mirror of
https://github.com/transmission/transmission.git
synced 2025-12-20 02:18:42 +00:00
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:
@@ -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));
|
||||
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),
|
||||
* 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_) +
|
||||
@@ -121,9 +130,11 @@ ReadState tr_handshake::read_yb(tr_peerIo* peer_io)
|
||||
}
|
||||
|
||||
/* send it */
|
||||
set_state(State::AwaitingVc);
|
||||
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,
|
||||
@@ -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));
|
||||
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
|
||||
tr_logAddTraceHand(this, "sending B->A: Diffie Hellman Yb, PadB");
|
||||
send_public_key_and_pad<PadbMaxlen>(peer_io);
|
||||
|
||||
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)
|
||||
|
||||
@@ -89,6 +89,66 @@ public:
|
||||
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:
|
||||
enum class State : uint8_t
|
||||
{
|
||||
@@ -110,7 +170,7 @@ private:
|
||||
AwaitingPadD
|
||||
};
|
||||
|
||||
///
|
||||
// ---
|
||||
|
||||
ReadState read_crypto_provide(tr_peerIo*);
|
||||
ReadState read_crypto_select(tr_peerIo*);
|
||||
@@ -192,77 +252,6 @@ private:
|
||||
bool fire_done(bool is_connected);
|
||||
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 inline auto dh_pool = small::max_size_vector<DH, DhPoolMaxSize>{};
|
||||
static inline auto dh_pool_mutex = std::mutex{};
|
||||
@@ -317,10 +306,12 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
std::optional<DH> dh_;
|
||||
|
||||
// ---
|
||||
|
||||
static auto constexpr HandshakeTimeoutSec = std::chrono::seconds{ 30 };
|
||||
|
||||
DoneFunc on_done_;
|
||||
|
||||
std::optional<tr_peer_id_t> peer_id_;
|
||||
@@ -335,6 +326,11 @@ private:
|
||||
|
||||
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_provide_ = {};
|
||||
uint16_t pad_c_len_ = {};
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
#include <cstddef> // size_t, std::byte
|
||||
#include <cstdint> // uint8_t
|
||||
@@ -43,11 +42,7 @@
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define LOCAL_SOCKETPAIR_AF AF_INET
|
||||
#else
|
||||
#define LOCAL_SOCKETPAIR_AF AF_UNIX
|
||||
#endif
|
||||
#define LOCAL_SOCKETPAIR_AF TR_IF_WIN32(AF_INET, AF_UNIX)
|
||||
|
||||
namespace libtransmission::test
|
||||
{
|
||||
@@ -123,7 +118,7 @@ public:
|
||||
void setPrivateKeyFromBase64(std::string_view 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_));
|
||||
}
|
||||
|
||||
@@ -141,12 +136,8 @@ public:
|
||||
|
||||
while (len > 0)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
auto const n = send(sock, reinterpret_cast<char const*>(walk), len, 0);
|
||||
#else
|
||||
auto const n = write(sock, walk, len);
|
||||
#endif
|
||||
assert(n >= 0);
|
||||
TR_ASSERT(n >= 0);
|
||||
len -= n;
|
||||
walk += n;
|
||||
}
|
||||
@@ -174,6 +165,8 @@ public:
|
||||
{
|
||||
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_make_socket_nonblocking(sockpair[0]));
|
||||
EXPECT_EQ(0, evutil_make_socket_nonblocking(sockpair[1]));
|
||||
return std::pair{ tr_peerIo::new_incoming(
|
||||
session,
|
||||
&session->top_bandwidth_,
|
||||
@@ -185,6 +178,8 @@ public:
|
||||
{
|
||||
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_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*/);
|
||||
peer_io->set_socket(tr_peer_socket(session, DefaultPeerSockAddr, sockpair[0]));
|
||||
return std::pair{ std::move(peer_io), sockpair[1] };
|
||||
@@ -208,24 +203,20 @@ public:
|
||||
return peer_id;
|
||||
}
|
||||
|
||||
static auto runHandshake(
|
||||
static auto startHandshake(
|
||||
std::optional<tr_handshake::Result>& result,
|
||||
tr_handshake::Mediator* mediator,
|
||||
std::shared_ptr<tr_peerIo> const& peer_io,
|
||||
tr_encryption_mode encryption_mode = TR_CLEAR_PREFERRED)
|
||||
{
|
||||
auto result = std::optional<tr_handshake::Result>{};
|
||||
|
||||
auto handshake = tr_handshake{ mediator,
|
||||
peer_io,
|
||||
encryption_mode,
|
||||
[&result](auto const& resin)
|
||||
{
|
||||
result = resin;
|
||||
return true;
|
||||
} };
|
||||
|
||||
waitFor([&result]() { return result.has_value(); }, MaxWaitMsec);
|
||||
return result;
|
||||
return tr_handshake{ mediator,
|
||||
peer_io,
|
||||
encryption_mode,
|
||||
[&result](auto const& resin)
|
||||
{
|
||||
result = resin;
|
||||
return true;
|
||||
} };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -250,11 +241,12 @@ TEST_F(HandshakeTest, incomingPlaintext)
|
||||
sendToClient(sock, TorrentWeAreSeeding.info_hash);
|
||||
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
|
||||
EXPECT_TRUE(res.has_value());
|
||||
assert(res.has_value());
|
||||
ASSERT_TRUE(res.has_value());
|
||||
EXPECT_TRUE(res->is_connected);
|
||||
EXPECT_TRUE(res->read_anything_from_peer);
|
||||
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, 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
|
||||
EXPECT_TRUE(res.has_value());
|
||||
assert(res.has_value());
|
||||
ASSERT_TRUE(res.has_value());
|
||||
EXPECT_FALSE(res->is_connected);
|
||||
EXPECT_TRUE(res->read_anything_from_peer);
|
||||
EXPECT_EQ(io, res->io);
|
||||
@@ -304,11 +297,12 @@ TEST_F(HandshakeTest, outgoingPlaintext)
|
||||
sendToClient(sock, UbuntuTorrent.info_hash);
|
||||
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
|
||||
EXPECT_TRUE(res.has_value());
|
||||
assert(res.has_value());
|
||||
ASSERT_TRUE(res.has_value());
|
||||
EXPECT_TRUE(res->is_connected);
|
||||
EXPECT_TRUE(res->read_anything_from_peer);
|
||||
EXPECT_EQ(io, res->io);
|
||||
@@ -330,24 +324,42 @@ TEST_F(HandshakeTest, incomingEncrypted)
|
||||
|
||||
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
|
||||
// in the wild for replay here
|
||||
// in the wild for replay here. This test will play as the peer.
|
||||
// 1. Peer sends Ya.
|
||||
sendB64ToClient(
|
||||
sock,
|
||||
"svkySIFcCsrDTeHjPt516UFbsoR+5vfbe5/m6stE7u5JLZ10kJ19NmP64E10qI"
|
||||
"nn78sCrJgjw1yEHHwrzOcKiRlYvcMotzJMe+SjrFUnaw3KBfn2bcKBhxb/sfM9"
|
||||
"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(
|
||||
sock,
|
||||
"ICAgICAgICAgIKdr4jIBZ4xFfO4xNiRV7Gl2azTSuTFuu06NU1WyRPif018JYe"
|
||||
"VGwrTPstEPu3V5lmzjtMGVLaL5EErlpJ93Xrz+ea6EIQEUZA+D4jKaV/to9NVi"
|
||||
"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
|
||||
EXPECT_TRUE(res.has_value());
|
||||
assert(res.has_value());
|
||||
ASSERT_TRUE(res.has_value());
|
||||
EXPECT_TRUE(res->is_connected);
|
||||
EXPECT_TRUE(res->read_anything_from_peer);
|
||||
EXPECT_EQ(io, res->io);
|
||||
@@ -368,24 +380,42 @@ TEST_F(HandshakeTest, incomingEncryptedUnknownInfoHash)
|
||||
|
||||
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
|
||||
// in the wild for replay here
|
||||
// in the wild for replay here. This test will play as the peer.
|
||||
// 1. Peer sends Ya.
|
||||
sendB64ToClient(
|
||||
sock,
|
||||
"svkySIFcCsrDTeHjPt516UFbsoR+5vfbe5/m6stE7u5JLZ10kJ19NmP64E10qI"
|
||||
"nn78sCrJgjw1yEHHwrzOcKiRlYvcMotzJMe+SjrFUnaw3KBfn2bcKBhxb/sfM9"
|
||||
"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(
|
||||
sock,
|
||||
"ICAgICAgICAgIKdr4jIBZ4xFfO4xNiRV7Gl2azTSuTFuu06NU1WyRPif018JYe"
|
||||
"VGwrTPstEPu3V5lmzjtMGVLaL5EErlpJ93Xrz+ea6EIQEUZA+D4jKaV/to9NVi"
|
||||
"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
|
||||
EXPECT_TRUE(res.has_value());
|
||||
assert(res.has_value());
|
||||
ASSERT_TRUE(res.has_value());
|
||||
EXPECT_FALSE(res->is_connected);
|
||||
EXPECT_TRUE(res->read_anything_from_peer);
|
||||
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 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
|
||||
// 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(
|
||||
sock,
|
||||
"Sfgoq/nrQfD4Iwirfk+uhOmQMOC/QwK/vYiOact1NF9TpWXms3cvlKEKxs0VU"
|
||||
"mnmytRh9bh4Lcs1bswlC6R05XrJGzLhZqAqcLUUAR1VTLA5oKSjR1038zFbhn"
|
||||
"c71jqlpney15ChMTnx02Qt+88l0Z9OWLUUJrUVy+OoIaTMSKDDFVOjuj0y+Ii"
|
||||
"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);
|
||||
"c71jql"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
|
||||
EXPECT_TRUE(res.has_value());
|
||||
assert(res.has_value());
|
||||
ASSERT_TRUE(res.has_value());
|
||||
EXPECT_TRUE(res->is_connected);
|
||||
EXPECT_TRUE(res->read_anything_from_peer);
|
||||
EXPECT_EQ(io, res->io);
|
||||
|
||||
Reference in New Issue
Block a user