mirror of
https://github.com/SerenityOS/serenity
synced 2026-04-25 17:15:42 +02:00
122 lines
3.8 KiB
C++
122 lines
3.8 KiB
C++
/*
|
|
* Copyright (c) 2026, Lucas Chollet <lucas.chollet@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/MemoryStream.h>
|
|
#include <LibSSH/Peer.h>
|
|
#include <LibTest/TestCase.h>
|
|
|
|
class SocketMock final : public Core::Socket {
|
|
public:
|
|
SocketMock(AllocatingMemoryStream& stream)
|
|
: stream(stream)
|
|
{
|
|
}
|
|
|
|
// ^AK::Stream
|
|
ErrorOr<Bytes> read_some(Bytes) override { VERIFY_NOT_REACHED(); }
|
|
ErrorOr<size_t> write_some(ReadonlyBytes bytes) override
|
|
{
|
|
return stream.write_some(bytes);
|
|
}
|
|
|
|
bool is_eof() const override { VERIFY_NOT_REACHED(); }
|
|
bool is_open() const override { return m_is_open; }
|
|
void close() override { m_is_open = false; }
|
|
|
|
// ^Core::Socket
|
|
ErrorOr<size_t> pending_bytes() const override { VERIFY_NOT_REACHED(); }
|
|
ErrorOr<bool> can_read_without_blocking(int) const override { VERIFY_NOT_REACHED(); }
|
|
ErrorOr<void> set_blocking(bool) override { VERIFY_NOT_REACHED(); }
|
|
ErrorOr<void> set_close_on_exec(bool) override { VERIFY_NOT_REACHED(); }
|
|
|
|
AllocatingMemoryStream& stream;
|
|
bool m_is_open { true };
|
|
};
|
|
|
|
class PeerMock final : public SSH::Peer {
|
|
public:
|
|
PeerMock(SocketMock& socket)
|
|
: SSH::Peer(socket)
|
|
{
|
|
Crypto::Hash::Digest<256> hash;
|
|
fill_with_random(hash.data);
|
|
set_hash(hash);
|
|
auto shared_secret = MUST(ByteBuffer::copy(hash.bytes()));
|
|
set_shared_secret(move(shared_secret));
|
|
}
|
|
|
|
using SSH::Peer::handle_disconnect_message;
|
|
using SSH::Peer::handle_new_keys_message;
|
|
using SSH::Peer::read_packet;
|
|
using SSH::Peer::send_new_keys_message;
|
|
using SSH::Peer::session_id;
|
|
using SSH::Peer::set_hash;
|
|
};
|
|
|
|
// Copied from wireshark, sniffed from a connection between an openssh server and client.
|
|
static constexpr auto new_keys_message = "\000\000\000\f\n\025\000\000\000\000\000\000\000\000\000\000"sv;
|
|
|
|
TEST_CASE(new_keys)
|
|
{
|
|
AllocatingMemoryStream stream;
|
|
SocketMock socket(stream);
|
|
auto peer = PeerMock(socket);
|
|
|
|
TRY_OR_FAIL(peer.send_new_keys_message());
|
|
auto written_packet = TRY_OR_FAIL(stream.read_until_eof());
|
|
EXPECT_EQ(written_packet.size(), 16u);
|
|
// The packet includes random bytes at the end.
|
|
u8 size_without_padding = 6;
|
|
EXPECT_EQ(written_packet.bytes().trim(size_without_padding), new_keys_message.bytes().trim(size_without_padding));
|
|
|
|
auto message = TRY_OR_FAIL(ByteBuffer::copy(new_keys_message.bytes()));
|
|
TRY_OR_FAIL(peer.handle_new_keys_message(message));
|
|
}
|
|
|
|
TEST_CASE(consecutive_packets)
|
|
{
|
|
AllocatingMemoryStream stream;
|
|
SocketMock socket(stream);
|
|
auto peer = PeerMock(socket);
|
|
|
|
auto input = TRY_OR_FAIL(ByteBuffer::copy(new_keys_message.bytes()));
|
|
auto additional_data = "This is additional data that should still be present at the end of the test!"sv;
|
|
|
|
input.append(additional_data.bytes());
|
|
|
|
TRY_OR_FAIL(peer.read_packet(input));
|
|
EXPECT_EQ(input.bytes(), additional_data.bytes());
|
|
}
|
|
|
|
TEST_CASE(disconnect_packet)
|
|
{
|
|
AllocatingMemoryStream stream;
|
|
SocketMock socket(stream);
|
|
auto peer = PeerMock(socket);
|
|
|
|
auto raw_message = "\x01\x00\x00\x00\x0b\x00\x00\x00\x14\x64\x69\x73\x63\x6f\x6e\x6e\x65\x63\x74\x65\x64\x20\x62\x79\x20\x75\x73\x65\x72\x00\x00\x00\x00"sv;
|
|
auto message = TRY_OR_FAIL(ByteBuffer::copy(raw_message.bytes()));
|
|
|
|
TRY_OR_FAIL(peer.handle_disconnect_message(message));
|
|
EXPECT(!socket.is_open());
|
|
}
|
|
|
|
TEST_CASE(stable_session_id)
|
|
{
|
|
// A session ID is stable, even after the peers rekeyed.
|
|
AllocatingMemoryStream stream;
|
|
SocketMock socket(stream);
|
|
auto peer = PeerMock(socket);
|
|
|
|
auto session_id = peer.session_id();
|
|
|
|
Crypto::Hash::Digest<256> new_hash;
|
|
fill_with_random({ new_hash.data, sizeof(new_hash.data) });
|
|
peer.set_hash(new_hash);
|
|
|
|
EXPECT_EQ(peer.session_id(), session_id);
|
|
}
|