Consume early pad a/b, improve handshake tests (#6987)

* properly consume PadA in MSE handshake, check for invalid Ya+PadA

* refactor: make handshake constants public (needed for test coverage)

* test: split test MSE handshakes by blocking steps

* test: use `ASSERT_TRUE` instead of `assert`

* test: fix windows crash by using `recv` and `send`

Co-authored-by: Yat Ho <46261767+tearfur@users.noreply.github.com>

* refactor: use `TR_IF_WIN32` for `LOCAL_SOCKETPAIR_AF`

Co-authored-by: Yat Ho <46261767+tearfur@users.noreply.github.com>

---------

Co-authored-by: Yat Ho <lagoho7@gmail.com>
Co-authored-by: reardonia <reardonia@github.com>
Co-authored-by: Yat Ho <46261767+tearfur@users.noreply.github.com>
This commit is contained in:
reardonia
2025-01-09 11:26:48 -05:00
committed by GitHub
parent cbba2e0390
commit 50eacf6933
3 changed files with 233 additions and 135 deletions

View File

@@ -75,6 +75,15 @@ ReadState tr_handshake::read_yb(tr_peerIo* peer_io)
peer_io->read_bytes(std::data(peer_public_key), std::size(peer_public_key)); peer_io->read_bytes(std::data(peer_public_key), std::size(peer_public_key));
get_dh().setPeerPublicKey(peer_public_key); get_dh().setPeerPublicKey(peer_public_key);
// everything received so far is Yb+PadB; peer has not yet sent VC for resync.
// so throw away buffer, and do early exit check: we know it's not legit MSE if > max PadB
pad_b_recv_len_ = peer_io->read_buffer_size();
if (pad_b_recv_len_ > PadbMaxlen)
{
return done(false);
}
peer_io->read_buffer_discard(pad_b_recv_len_);
/* now send these: HASH('req1', S), HASH('req2', SKEY) xor HASH('req3', S), /* now send these: HASH('req1', S), HASH('req2', SKEY) xor HASH('req3', S),
* ENCRYPT(VC, crypto_provide, len(PadC), PadC, len(IA)), ENCRYPT(IA) */ * ENCRYPT(VC, crypto_provide, len(PadC), PadC, len(IA)), ENCRYPT(IA) */
static auto constexpr BufSize = (std::tuple_size_v<tr_sha1_digest_t> * 2U) + std::size(VC) + sizeof(crypto_provide_) + static auto constexpr BufSize = (std::tuple_size_v<tr_sha1_digest_t> * 2U) + std::size(VC) + sizeof(crypto_provide_) +
@@ -121,9 +130,11 @@ ReadState tr_handshake::read_yb(tr_peerIo* peer_io)
} }
/* send it */ /* send it */
set_state(State::AwaitingVc);
peer_io->write(outbuf, false); peer_io->write(outbuf, false);
return ReadState::Now;
set_state(State::AwaitingVc);
// LATER, not NOW: recv buffer was just drained and peer was blocking
return ReadState::Later;
} }
// MSE spec: "Since the length of [PadB is] unknown, // MSE spec: "Since the length of [PadB is] unknown,
@@ -369,12 +380,22 @@ ReadState tr_handshake::read_ya(tr_peerIo* peer_io)
peer_io->read_bytes(std::data(peer_public_key), std::size(peer_public_key)); peer_io->read_bytes(std::data(peer_public_key), std::size(peer_public_key));
get_dh().setPeerPublicKey(peer_public_key); get_dh().setPeerPublicKey(peer_public_key);
// everything received so far is Ya+PadA; haven't sent Yb and peer has not yet sent HASH('req1').
// so throw away buffer, and do early exit check: we know it's not legit MSE if > max PadA
pad_a_recv_len_ = peer_io->read_buffer_size();
if (pad_a_recv_len_ > PadaMaxlen)
{
return done(false);
}
peer_io->read_buffer_discard(pad_a_recv_len_);
// send our public key to the peer // send our public key to the peer
tr_logAddTraceHand(this, "sending B->A: Diffie Hellman Yb, PadB"); tr_logAddTraceHand(this, "sending B->A: Diffie Hellman Yb, PadB");
send_public_key_and_pad<PadbMaxlen>(peer_io); send_public_key_and_pad<PadbMaxlen>(peer_io);
set_state(State::AwaitingPadA); set_state(State::AwaitingPadA);
return ReadState::Now; // LATER, not NOW: recv buffer was just drained and peer was blocking
return ReadState::Later;
} }
ReadState tr_handshake::read_pad_a(tr_peerIo* peer_io) ReadState tr_handshake::read_pad_a(tr_peerIo* peer_io)

View File

@@ -89,6 +89,66 @@ public:
maybe_recycle_dh(); maybe_recycle_dh();
} }
// bittorrent handshake constants
// https://www.bittorrent.org/beps/bep_0003.html#peer-protocol
// https://wiki.theory.org/BitTorrentSpecification#Handshake
// > The handshake starts with character ninteen (decimal) followed by the string
// > 'BitTorrent protocol'. The leading character is a length prefix.
static auto constexpr HandshakeName = std::array<std::byte, 20>{
std::byte{ 19 }, std::byte{ 'B' }, std::byte{ 'i' }, std::byte{ 't' }, std::byte{ 'T' },
std::byte{ 'o' }, std::byte{ 'r' }, std::byte{ 'r' }, std::byte{ 'e' }, std::byte{ 'n' },
std::byte{ 't' }, std::byte{ ' ' }, std::byte{ 'p' }, std::byte{ 'r' }, std::byte{ 'o' },
std::byte{ 't' }, std::byte{ 'o' }, std::byte{ 'c' }, std::byte{ 'o' }, std::byte{ 'l' }
};
// [Next comes] eight reserved bytes [used for enabling ltep, dht, fext]
static auto constexpr HandshakeFlagsBytes = size_t{ 8 };
static auto constexpr HandshakeFlagsBits = size_t{ 64 };
// https://www.bittorrent.org/beps/bep_0004.html
// https://wiki.theory.org/BitTorrentSpecification#Reserved_Bytes
static auto constexpr LtepFlag = size_t{ 43U };
static auto constexpr FextFlag = size_t{ 61U };
static auto constexpr DhtFlag = size_t{ 63U };
// Next comes the 20 byte sha1 info_hash and the 20-byte peer_id
static auto constexpr HandshakeSize = std::size(HandshakeName) + HandshakeFlagsBytes + std::tuple_size_v<tr_sha1_digest_t> +
std::tuple_size_v<tr_peer_id_t>;
static_assert(HandshakeSize == 68U);
// Length of handhshake up through the info_hash. From theory.org:
// > The recipient may wait for the initiator's handshake... however,
// > the recipient must respond as soon as it sees the info_hash part
// > of the handshake (the peer id will presumably be sent after the
// > recipient sends its own handshake).
static auto constexpr IncomingHandshakeLen = std::size(HandshakeName) + HandshakeFlagsBytes +
std::tuple_size_v<tr_sha1_digest_t>;
static_assert(IncomingHandshakeLen == 48U);
// MSE constants.
// http://wiki.vuze.com/w/Message_Stream_Encryption
// > crypto_provide and crypto_select are a 32bit bitfields.
// > As of now 0x01 means plaintext, 0x02 means RC4. (see Functions)
// > The remaining bits are reserved for future use.
static auto constexpr CryptoProvidePlaintext = uint32_t{ 0x01 };
static auto constexpr CryptoProvideRC4 = uint32_t{ 0x02 };
// MSE constants.
// http://wiki.vuze.com/w/Message_Stream_Encryption
// > PadA, PadB: Random data with a random length of 0 to 512 bytes each
// > PadC, PadD: Arbitrary data with a length of 0 to 512 bytes
static auto constexpr PadaMaxlen = uint16_t{ 512U };
static auto constexpr PadbMaxlen = uint16_t{ 512U };
static auto constexpr PadcMaxlen = uint16_t{ 512U };
static auto constexpr PaddMaxlen = uint16_t{ 512U };
// "VC is a verification constant that is used to verify whether the
// other side knows S and SKEY and thus defeats replay attacks of the
// SKEY hash. As of this version VC is a String of 8 bytes set to 0x00."
// https://wiki.vuze.com/w/Message_Stream_Encryption
using vc_t = std::array<std::byte, 8>;
static auto constexpr VC = vc_t{};
private: private:
enum class State : uint8_t enum class State : uint8_t
{ {
@@ -110,7 +170,7 @@ private:
AwaitingPadD AwaitingPadD
}; };
/// // ---
ReadState read_crypto_provide(tr_peerIo*); ReadState read_crypto_provide(tr_peerIo*);
ReadState read_crypto_select(tr_peerIo*); ReadState read_crypto_select(tr_peerIo*);
@@ -192,77 +252,6 @@ private:
bool fire_done(bool is_connected); bool fire_done(bool is_connected);
void fire_timer(); void fire_timer();
///
static auto constexpr HandshakeTimeoutSec = std::chrono::seconds{ 30 };
// bittorrent handshake constants
// https://www.bittorrent.org/beps/bep_0003.html#peer-protocol
// https://wiki.theory.org/BitTorrentSpecification#Handshake
// > The handshake starts with character ninteen (decimal) followed by the string
// > 'BitTorrent protocol'. The leading character is a length prefix.
static auto constexpr HandshakeName = std::array<std::byte, 20>{
std::byte{ 19 }, std::byte{ 'B' }, std::byte{ 'i' }, std::byte{ 't' }, std::byte{ 'T' },
std::byte{ 'o' }, std::byte{ 'r' }, std::byte{ 'r' }, std::byte{ 'e' }, std::byte{ 'n' },
std::byte{ 't' }, std::byte{ ' ' }, std::byte{ 'p' }, std::byte{ 'r' }, std::byte{ 'o' },
std::byte{ 't' }, std::byte{ 'o' }, std::byte{ 'c' }, std::byte{ 'o' }, std::byte{ 'l' }
};
// [Next comes] eight reserved bytes [used for enabling ltep, dht, fext]
static auto constexpr HandshakeFlagsBytes = size_t{ 8 };
static auto constexpr HandshakeFlagsBits = size_t{ 64 };
// https://www.bittorrent.org/beps/bep_0004.html
// https://wiki.theory.org/BitTorrentSpecification#Reserved_Bytes
static auto constexpr LtepFlag = size_t{ 43U };
static auto constexpr FextFlag = size_t{ 61U };
static auto constexpr DhtFlag = size_t{ 63U };
// Next comes the 20 byte sha1 info_hash and the 20-byte peer_id
static auto constexpr HandshakeSize = std::size(HandshakeName) + HandshakeFlagsBytes + std::tuple_size_v<tr_sha1_digest_t> +
std::tuple_size_v<tr_peer_id_t>;
static_assert(HandshakeSize == 68U);
// Length of handhshake up through the info_hash. From theory.org:
// > The recipient may wait for the initiator's handshake... however,
// > the recipient must respond as soon as it sees the info_hash part
// > of the handshake (the peer id will presumably be sent after the
// > recipient sends its own handshake).
static auto constexpr IncomingHandshakeLen = std::size(HandshakeName) + HandshakeFlagsBytes +
std::tuple_size_v<tr_sha1_digest_t>;
static_assert(IncomingHandshakeLen == 48U);
// MSE constants.
// http://wiki.vuze.com/w/Message_Stream_Encryption
// > crypto_provide and crypto_select are a 32bit bitfields.
// > As of now 0x01 means plaintext, 0x02 means RC4. (see Functions)
// > The remaining bits are reserved for future use.
static auto constexpr CryptoProvidePlaintext = uint32_t{ 0x01 };
static auto constexpr CryptoProvideRC4 = uint32_t{ 0x02 };
// MSE constants.
// http://wiki.vuze.com/w/Message_Stream_Encryption
// > PadA, PadB: Random data with a random length of 0 to 512 bytes each
// > PadC, PadD: Arbitrary data with a length of 0 to 512 bytes
static auto constexpr PadaMaxlen = uint16_t{ 512U };
static auto constexpr PadbMaxlen = uint16_t{ 512U };
static auto constexpr PadcMaxlen = uint16_t{ 512U };
static auto constexpr PaddMaxlen = uint16_t{ 512U };
// "VC is a verification constant that is used to verify whether the
// other side knows S and SKEY and thus defeats replay attacks of the
// SKEY hash. As of this version VC is a String of 8 bytes set to 0x00."
// https://wiki.vuze.com/w/Message_Stream_Encryption
using vc_t = std::array<std::byte, 8>;
static auto constexpr VC = vc_t{};
// Used when resynchronizing in read_vc(). This value is cached to avoid
// the cost of recomputing it. MSE spec: "Since the length of [PadB is]
// unknown, A will be able to resynchronize on ENCRYPT(VC)".
std::optional<vc_t> encrypted_vc_;
///
static constexpr auto DhPoolMaxSize = size_t{ 32 }; static constexpr auto DhPoolMaxSize = size_t{ 32 };
static inline auto dh_pool = small::max_size_vector<DH, DhPoolMaxSize>{}; static inline auto dh_pool = small::max_size_vector<DH, DhPoolMaxSize>{};
static inline auto dh_pool_mutex = std::mutex{}; static inline auto dh_pool_mutex = std::mutex{};
@@ -317,10 +306,12 @@ private:
} }
} }
///
std::optional<DH> dh_; std::optional<DH> dh_;
// ---
static auto constexpr HandshakeTimeoutSec = std::chrono::seconds{ 30 };
DoneFunc on_done_; DoneFunc on_done_;
std::optional<tr_peer_id_t> peer_id_; std::optional<tr_peer_id_t> peer_id_;
@@ -335,6 +326,11 @@ private:
tr_encryption_mode encryption_mode_; tr_encryption_mode encryption_mode_;
// Used when resynchronizing in read_vc(). This value is cached to avoid
// the cost of recomputing it. MSE spec: "Since the length of [PadB is]
// unknown, A will be able to resynchronize on ENCRYPT(VC)".
std::optional<vc_t> encrypted_vc_;
uint32_t crypto_select_ = {}; uint32_t crypto_select_ = {};
uint32_t crypto_provide_ = {}; uint32_t crypto_provide_ = {};
uint16_t pad_c_len_ = {}; uint16_t pad_c_len_ = {};

View File

@@ -5,7 +5,6 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cassert>
#include <cerrno> #include <cerrno>
#include <cstddef> // size_t, std::byte #include <cstddef> // size_t, std::byte
#include <cstdint> // uint8_t #include <cstdint> // uint8_t
@@ -43,11 +42,7 @@
using namespace std::literals; using namespace std::literals;
#ifdef _WIN32 #define LOCAL_SOCKETPAIR_AF TR_IF_WIN32(AF_INET, AF_UNIX)
#define LOCAL_SOCKETPAIR_AF AF_INET
#else
#define LOCAL_SOCKETPAIR_AF AF_UNIX
#endif
namespace libtransmission::test namespace libtransmission::test
{ {
@@ -123,7 +118,7 @@ public:
void setPrivateKeyFromBase64(std::string_view b64) void setPrivateKeyFromBase64(std::string_view b64)
{ {
auto const str = tr_base64_decode(b64); auto const str = tr_base64_decode(b64);
assert(std::size(str) == std::size(private_key_)); TR_ASSERT(std::size(str) == std::size(private_key_));
std::copy_n(reinterpret_cast<std::byte const*>(std::data(str)), std::size(str), std::begin(private_key_)); std::copy_n(reinterpret_cast<std::byte const*>(std::data(str)), std::size(str), std::begin(private_key_));
} }
@@ -141,12 +136,8 @@ public:
while (len > 0) while (len > 0)
{ {
#if defined(_WIN32)
auto const n = send(sock, reinterpret_cast<char const*>(walk), len, 0); auto const n = send(sock, reinterpret_cast<char const*>(walk), len, 0);
#else TR_ASSERT(n >= 0);
auto const n = write(sock, walk, len);
#endif
assert(n >= 0);
len -= n; len -= n;
walk += n; walk += n;
} }
@@ -174,6 +165,8 @@ public:
{ {
auto sockpair = std::array<evutil_socket_t, 2>{ -1, -1 }; auto sockpair = std::array<evutil_socket_t, 2>{ -1, -1 };
EXPECT_EQ(0, evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0, std::data(sockpair))) << tr_strerror(errno); EXPECT_EQ(0, evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0, std::data(sockpair))) << tr_strerror(errno);
EXPECT_EQ(0, evutil_make_socket_nonblocking(sockpair[0]));
EXPECT_EQ(0, evutil_make_socket_nonblocking(sockpair[1]));
return std::pair{ tr_peerIo::new_incoming( return std::pair{ tr_peerIo::new_incoming(
session, session,
&session->top_bandwidth_, &session->top_bandwidth_,
@@ -185,6 +178,8 @@ public:
{ {
auto sockpair = std::array<evutil_socket_t, 2>{ -1, -1 }; auto sockpair = std::array<evutil_socket_t, 2>{ -1, -1 };
EXPECT_EQ(0, evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0, std::data(sockpair))) << tr_strerror(errno); EXPECT_EQ(0, evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0, std::data(sockpair))) << tr_strerror(errno);
EXPECT_EQ(0, evutil_make_socket_nonblocking(sockpair[0]));
EXPECT_EQ(0, evutil_make_socket_nonblocking(sockpair[1]));
auto peer_io = tr_peerIo::create(session, &session->top_bandwidth_, &info_hash, false /*incoming*/, false /*seed*/); auto peer_io = tr_peerIo::create(session, &session->top_bandwidth_, &info_hash, false /*incoming*/, false /*seed*/);
peer_io->set_socket(tr_peer_socket(session, DefaultPeerSockAddr, sockpair[0])); peer_io->set_socket(tr_peer_socket(session, DefaultPeerSockAddr, sockpair[0]));
return std::pair{ std::move(peer_io), sockpair[1] }; return std::pair{ std::move(peer_io), sockpair[1] };
@@ -208,24 +203,20 @@ public:
return peer_id; return peer_id;
} }
static auto runHandshake( static auto startHandshake(
std::optional<tr_handshake::Result>& result,
tr_handshake::Mediator* mediator, tr_handshake::Mediator* mediator,
std::shared_ptr<tr_peerIo> const& peer_io, std::shared_ptr<tr_peerIo> const& peer_io,
tr_encryption_mode encryption_mode = TR_CLEAR_PREFERRED) tr_encryption_mode encryption_mode = TR_CLEAR_PREFERRED)
{ {
auto result = std::optional<tr_handshake::Result>{}; return tr_handshake{ mediator,
peer_io,
auto handshake = tr_handshake{ mediator, encryption_mode,
peer_io, [&result](auto const& resin)
encryption_mode, {
[&result](auto const& resin) result = resin;
{ return true;
result = resin; } };
return true;
} };
waitFor([&result]() { return result.has_value(); }, MaxWaitMsec);
return result;
} }
}; };
@@ -250,11 +241,12 @@ TEST_F(HandshakeTest, incomingPlaintext)
sendToClient(sock, TorrentWeAreSeeding.info_hash); sendToClient(sock, TorrentWeAreSeeding.info_hash);
sendToClient(sock, peer_id); sendToClient(sock, peer_id);
auto const res = runHandshake(&mediator, io); auto res = std::optional<tr_handshake::Result>{};
auto const handshake = startHandshake(res, &mediator, io);
waitFor([&res] { return res.has_value(); }, MaxWaitMsec);
// check the results // check the results
EXPECT_TRUE(res.has_value()); ASSERT_TRUE(res.has_value());
assert(res.has_value());
EXPECT_TRUE(res->is_connected); EXPECT_TRUE(res->is_connected);
EXPECT_TRUE(res->read_anything_from_peer); EXPECT_TRUE(res->read_anything_from_peer);
EXPECT_EQ(io, res->io); EXPECT_EQ(io, res->io);
@@ -278,11 +270,12 @@ TEST_F(HandshakeTest, incomingPlaintextUnknownInfoHash)
sendToClient(sock, tr_sha1::digest("some other torrent unknown to us"sv)); sendToClient(sock, tr_sha1::digest("some other torrent unknown to us"sv));
sendToClient(sock, makeRandomPeerId()); sendToClient(sock, makeRandomPeerId());
auto const res = runHandshake(&mediator, io); auto res = std::optional<tr_handshake::Result>{};
auto const handshake = startHandshake(res, &mediator, io);
waitFor([&res] { return res.has_value(); }, MaxWaitMsec);
// check the results // check the results
EXPECT_TRUE(res.has_value()); ASSERT_TRUE(res.has_value());
assert(res.has_value());
EXPECT_FALSE(res->is_connected); EXPECT_FALSE(res->is_connected);
EXPECT_TRUE(res->read_anything_from_peer); EXPECT_TRUE(res->read_anything_from_peer);
EXPECT_EQ(io, res->io); EXPECT_EQ(io, res->io);
@@ -304,11 +297,12 @@ TEST_F(HandshakeTest, outgoingPlaintext)
sendToClient(sock, UbuntuTorrent.info_hash); sendToClient(sock, UbuntuTorrent.info_hash);
sendToClient(sock, peer_id); sendToClient(sock, peer_id);
auto const res = runHandshake(&mediator, io); auto res = std::optional<tr_handshake::Result>{};
auto const handshake = startHandshake(res, &mediator, io);
waitFor([&res] { return res.has_value(); }, MaxWaitMsec);
// check the results // check the results
EXPECT_TRUE(res.has_value()); ASSERT_TRUE(res.has_value());
assert(res.has_value());
EXPECT_TRUE(res->is_connected); EXPECT_TRUE(res->is_connected);
EXPECT_TRUE(res->read_anything_from_peer); EXPECT_TRUE(res->read_anything_from_peer);
EXPECT_EQ(io, res->io); EXPECT_EQ(io, res->io);
@@ -330,24 +324,42 @@ TEST_F(HandshakeTest, incomingEncrypted)
auto [io, sock] = createIncomingIo(session_); auto [io, sock] = createIncomingIo(session_);
auto res = std::optional<tr_handshake::Result>{};
auto const handshake = startHandshake(res, &mediator, io);
// Peer->Client data from a successful encrypted handshake recorded // Peer->Client data from a successful encrypted handshake recorded
// in the wild for replay here // in the wild for replay here. This test will play as the peer.
// 1. Peer sends Ya.
sendB64ToClient( sendB64ToClient(
sock, sock,
"svkySIFcCsrDTeHjPt516UFbsoR+5vfbe5/m6stE7u5JLZ10kJ19NmP64E10qI" "svkySIFcCsrDTeHjPt516UFbsoR+5vfbe5/m6stE7u5JLZ10kJ19NmP64E10qI"
"nn78sCrJgjw1yEHHwrzOcKiRlYvcMotzJMe+SjrFUnaw3KBfn2bcKBhxb/sfM9" "nn78sCrJgjw1yEHHwrzOcKiRlYvcMotzJMe+SjrFUnaw3KBfn2bcKBhxb/sfM9"
"J7nJ"sv); "J7nJ"sv);
// 2. Wait for client to reply with Yb.
ASSERT_TRUE(waitFor(
[&s = std::as_const(sock), buf = std::array<char, 16>{}, n_read = size_t{}]() mutable
{
if (auto ret = recv(s, std::data(buf), std::size(buf), 0); ret > 0)
{
n_read += ret;
}
return n_read >= tr_handshake::DH::KeySize;
},
MaxWaitMsec));
// 3. Peer sends the rest of the handshake.
sendB64ToClient( sendB64ToClient(
sock, sock,
"ICAgICAgICAgIKdr4jIBZ4xFfO4xNiRV7Gl2azTSuTFuu06NU1WyRPif018JYe" "ICAgICAgICAgIKdr4jIBZ4xFfO4xNiRV7Gl2azTSuTFuu06NU1WyRPif018JYe"
"VGwrTPstEPu3V5lmzjtMGVLaL5EErlpJ93Xrz+ea6EIQEUZA+D4jKaV/to9NVi" "VGwrTPstEPu3V5lmzjtMGVLaL5EErlpJ93Xrz+ea6EIQEUZA+D4jKaV/to9NVi"
"04/1W1A2PHgg+I9puac/i9BsFPcjdQeoVtU73lNCbTDQgTieyjDWmwo="sv); "04/1W1A2PHgg+I9puac/i9BsFPcjdQeoVtU73lNCbTDQgTieyjDWmwo="sv);
auto const res = runHandshake(&mediator, io); // 4. Wait for handshake to complete.
waitFor([&res] { return res.has_value(); }, MaxWaitMsec);
// check the results // check the results
EXPECT_TRUE(res.has_value()); ASSERT_TRUE(res.has_value());
assert(res.has_value());
EXPECT_TRUE(res->is_connected); EXPECT_TRUE(res->is_connected);
EXPECT_TRUE(res->read_anything_from_peer); EXPECT_TRUE(res->read_anything_from_peer);
EXPECT_EQ(io, res->io); EXPECT_EQ(io, res->io);
@@ -368,24 +380,42 @@ TEST_F(HandshakeTest, incomingEncryptedUnknownInfoHash)
auto [io, sock] = createIncomingIo(session_); auto [io, sock] = createIncomingIo(session_);
auto res = std::optional<tr_handshake::Result>{};
auto const handshake = startHandshake(res, &mediator, io);
// Peer->Client data from a successful encrypted handshake recorded // Peer->Client data from a successful encrypted handshake recorded
// in the wild for replay here // in the wild for replay here. This test will play as the peer.
// 1. Peer sends Ya.
sendB64ToClient( sendB64ToClient(
sock, sock,
"svkySIFcCsrDTeHjPt516UFbsoR+5vfbe5/m6stE7u5JLZ10kJ19NmP64E10qI" "svkySIFcCsrDTeHjPt516UFbsoR+5vfbe5/m6stE7u5JLZ10kJ19NmP64E10qI"
"nn78sCrJgjw1yEHHwrzOcKiRlYvcMotzJMe+SjrFUnaw3KBfn2bcKBhxb/sfM9" "nn78sCrJgjw1yEHHwrzOcKiRlYvcMotzJMe+SjrFUnaw3KBfn2bcKBhxb/sfM9"
"J7nJ"sv); "J7nJ"sv);
// 2. Wait for client to reply with Yb.
ASSERT_TRUE(waitFor(
[&s = std::as_const(sock), buf = std::array<char, 16>{}, n_read = size_t{}]() mutable
{
if (auto ret = recv(s, std::data(buf), std::size(buf), 0); ret >= 0)
{
n_read += ret;
}
return n_read >= tr_handshake::DH::KeySize;
},
MaxWaitMsec));
// 3. Peer sends the rest of the handshake.
sendB64ToClient( sendB64ToClient(
sock, sock,
"ICAgICAgICAgIKdr4jIBZ4xFfO4xNiRV7Gl2azTSuTFuu06NU1WyRPif018JYe" "ICAgICAgICAgIKdr4jIBZ4xFfO4xNiRV7Gl2azTSuTFuu06NU1WyRPif018JYe"
"VGwrTPstEPu3V5lmzjtMGVLaL5EErlpJ93Xrz+ea6EIQEUZA+D4jKaV/to9NVi" "VGwrTPstEPu3V5lmzjtMGVLaL5EErlpJ93Xrz+ea6EIQEUZA+D4jKaV/to9NVi"
"04/1W1A2PHgg+I9puac/i9BsFPcjdQeoVtU73lNCbTDQgTieyjDWmwo="sv); "04/1W1A2PHgg+I9puac/i9BsFPcjdQeoVtU73lNCbTDQgTieyjDWmwo="sv);
auto const res = runHandshake(&mediator, io); // 4. Wait for handshake to complete.
waitFor([&res] { return res.has_value(); }, MaxWaitMsec);
// check the results // check the results
EXPECT_TRUE(res.has_value()); ASSERT_TRUE(res.has_value());
assert(res.has_value());
EXPECT_FALSE(res->is_connected); EXPECT_FALSE(res->is_connected);
EXPECT_TRUE(res->read_anything_from_peer); EXPECT_TRUE(res->read_anything_from_peer);
EXPECT_EQ(tr_sha1_digest_t{}, io->torrent_hash()); EXPECT_EQ(tr_sha1_digest_t{}, io->torrent_hash());
@@ -403,29 +433,80 @@ TEST_F(HandshakeTest, outgoingEncrypted)
auto [io, sock] = createOutgoingIo(session_, UbuntuTorrent.info_hash); auto [io, sock] = createOutgoingIo(session_, UbuntuTorrent.info_hash);
auto res = std::optional<tr_handshake::Result>{};
auto const handshake = startHandshake(res, &mediator, io, TR_ENCRYPTION_PREFERRED);
// Peer->Client data from a successful encrypted handshake recorded // Peer->Client data from a successful encrypted handshake recorded
// in the wild for replay here // in the wild for replay here. This test will play as the peer.
// 1. Wait for client to send Ya.
ASSERT_TRUE(waitFor(
[&s = std::as_const(sock), buf = std::array<char, 16>{}, n_read = size_t{}]() mutable
{
if (auto ret = recv(s, std::data(buf), std::size(buf), 0); ret >= 0)
{
n_read += ret;
}
return n_read >= tr_handshake::DH::KeySize;
},
MaxWaitMsec));
// 2. Peer replies with Yb.
sendB64ToClient( sendB64ToClient(
sock, sock,
"Sfgoq/nrQfD4Iwirfk+uhOmQMOC/QwK/vYiOact1NF9TpWXms3cvlKEKxs0VU" "Sfgoq/nrQfD4Iwirfk+uhOmQMOC/QwK/vYiOact1NF9TpWXms3cvlKEKxs0VU"
"mnmytRh9bh4Lcs1bswlC6R05XrJGzLhZqAqcLUUAR1VTLA5oKSjR1038zFbhn" "mnmytRh9bh4Lcs1bswlC6R05XrJGzLhZqAqcLUUAR1VTLA5oKSjR1038zFbhn"
"c71jqlpney15ChMTnx02Qt+88l0Z9OWLUUJrUVy+OoIaTMSKDDFVOjuj0y+Ii" "c71jql"sv);
"cE0ZnN61e0/R/g+APRK5tegw0SLZ3Nr8+y4Dl77sZyc141PR9xvDj0da1eAvf"
"BvXyyDem4vUjqiLUNCEV8KDXEMPCPYAQoDZzLvMyOEtJM/if0o0UN88SWtt1k"
"jRD8UNvUlXIfM0YsnJhKA6fJ7/4geK7+Wo2aicfaLFOyG5IEJbTg9OQYbDHFa"
"oVzD0xY0Dx+J0loqM+CzrPj8UpeXIcbD7pJrT3XPECbFQ12cCY5LW5RymVIx8"
"TP0ajGiTxou1L7DbGD54SYgV/4qFbafRsWp9AO+YDJcouFd/jiVN+r3loxvfT"
"0A9H9DRAMR0rZKpQpXZ1ZAhAuAOXGHFIvtw8wd6dPybeu5+LoR2S90/IpwHWI"
"jbNbypQZuA9hn4JfFMWPP9TG/E11loB4+MkrP22U72ezjL5ipd74AEEP0/u8w"
"Gj1t2kXhND9ONfasA+pY25y8GM04M0B7+0xKmsHP7tntwQLAGZATH83rOxaSO"
"3+o/RdiKQJAsGxMIU08scBc5VOmrAmjeYrLNpFnpXVuavH5if7490zMCu3DEn"
"G9hpbYbiX95T+EUcRbM6pSCvr3Twq1Q="sv);
auto const res = runHandshake(&mediator, io, TR_ENCRYPTION_PREFERRED); // 3. Wait for client to send HASH('req1', S).
static auto constexpr WantedLen = tr_handshake::PadbMaxlen + std::tuple_size_v<tr_sha1_digest_t>;
static auto constexpr NeedleBase64 = "mbpZFBwdi4U1snVvboN3sMEpNmU="sv;
auto const needle = tr_base64_decode(NeedleBase64);
auto buf = libtransmission::StackBuffer<WantedLen, char>{};
ASSERT_TRUE(waitFor(
[&s = sock, &buf, &needle, n_read = size_t{}]() mutable
{
static auto constexpr StepSize = 14U;
static_assert(WantedLen % StepSize == 0U);
while (n_read < WantedLen)
{
auto const [cur, curlen] = buf.reserve_space(StepSize);
auto const ret = recv(s, cur, curlen, 0);
if (ret <= 0)
{
return false;
}
buf.commit_space(ret);
n_read += ret;
if (auto const it = std::search(std::begin(buf), std::end(buf), std::begin(needle), std::end(needle));
it != std::end(buf))
{
return true;
}
}
return false;
},
MaxWaitMsec));
// 4. Peer sends the rest of the handshake.
sendB64ToClient(
sock,
"paZ3steQoTE58dNkLfvPJdGfTli1FCa1FcvjqCGkzEigwxVTo7o9MviInBNGZ"
"zetXtP0f4PgD0SubXoMNEi2dza/PsuA5e+7GcnNeNT0fcbw49HWtXgL3wb18s"
"g3puL1I6oi1DQhFfCg1xDDwj2AEKA2cy7zMjhLSTP4n9KNFDfPElrbdZI0Q/F"
"Db1JVyHzNGLJyYSgOnye/+IHiu/lqNmonH2ixTshuSBCW04PTkGGwxxWqFcw9"
"MWNA8fidJaKjPgs6z4/FKXlyHGw+6Sa091zxAmxUNdnAmOS1uUcplSMfEz9Go"
"xok8aLtS+w2xg+eEmIFf+KhW2n0bFqfQDvmAyXKLhXf44lTfq95aMb309APR/"
"Q0QDEdK2SqUKV2dWQIQLgDlxhxSL7cPMHenT8m3rufi6EdkvdPyKcB1iI2zW8"
"qUGbgPYZ+CXxTFjz/UxvxNdZaAePjJKz9tlO9ns4y+YqXe+ABBD9P7vMBo9bd"
"pF4TQ/TjX2rAPqWNucvBjNODNAe/tMSprBz+7Z7cECwBmQEx/N6zsWkjt/qP0"
"XYikCQLBsTCFNPLHAXOVTpqwJo3mKyzaRZ6V1bmrx+Yn++PdMzArtwxJxvYaW"
"2G4l/eU/hFHEWzOqUgr6908KtU"sv);
// 5. Wait for handshake to complete.
waitFor([&res] { return res.has_value(); }, MaxWaitMsec);
// check the results // check the results
EXPECT_TRUE(res.has_value()); ASSERT_TRUE(res.has_value());
assert(res.has_value());
EXPECT_TRUE(res->is_connected); EXPECT_TRUE(res->is_connected);
EXPECT_TRUE(res->read_anything_from_peer); EXPECT_TRUE(res->read_anything_from_peer);
EXPECT_EQ(io, res->io); EXPECT_EQ(io, res->io);