SSHServer: Add support for writing keylog files

These files are used by wireshark to decode SSH packets. This is really
useful for inspecting the traffic after the SSH peers started
encryption.

To use it, you need to pass --unsafe-keylog-file FILE when starting the
server and then make Wireshark's ssh.keylog_file setting point to the
same file.

Note that this option leaks secrets to the file system and thus is not
safe to use.

The file format is described here:
6add14a3f3/epan/dissectors/packet-ssh.c (L2684-2704)
This commit is contained in:
Lucas Chollet
2026-04-13 15:32:00 +02:00
parent 242fdddd1e
commit 900b879147
4 changed files with 22 additions and 3 deletions

View File

@@ -129,9 +129,9 @@ ErrorOr<void> SSHClient::send_key_protocol_message()
TRY(stream.write_value<u8>(to_underlying(MessageID::KEXINIT)));
auto cookie = TRY(ByteBuffer::create_uninitialized(16));
fill_with_random(cookie);
TRY(stream.write_until_depleted(cookie));
m_cookie = TRY(ByteBuffer::create_uninitialized(16));
fill_with_random(m_cookie);
TRY(stream.write_until_depleted(m_cookie));
TRY(encode_name_list(stream, KEX_ALGORITHMS));
TRY(encode_name_list(stream, SERVER_HOST_KEY_ALGORITHMS));
@@ -199,6 +199,15 @@ ErrorOr<void> SSHClient::send_ecdh_reply(ByteBuffer&& client_public_key)
// "Compute shared secret."
auto shared_secret = TRY(curve.compute_coordinate(private_key, client_public_key));
m_key_exchange_data.shared_secret = shared_secret;
if (auto keylog_file = ServerConfiguration::the().keylog_file(); keylog_file.has_value()) {
auto file = TRY(Core::File::open(*keylog_file, Core::File::OpenMode::Write | Core::File::OpenMode::Append));
TRY(file->write_until_depleted(ByteString::formatted("{:hex-dump}"sv, m_cookie.bytes())));
TRY(file->write_until_depleted(" SHARED_SECRET "sv));
TRY(file->write_until_depleted(ByteString::formatted("{:hex-dump}\n"sv, shared_secret.bytes())));
}
m_cookie = {};
set_shared_secret(move(shared_secret));
// FIXME: Abort if shared_point is not valid (at least when it's all zero, maybe there are other cases too).

View File

@@ -100,6 +100,7 @@ private:
Core::TCPSocket& m_tcp_socket;
KeyExchangeData m_key_exchange_data {};
ByteBuffer m_cookie {};
Vector<Session> m_sessions;
};

View File

@@ -31,6 +31,9 @@ public:
m_user_authorized_keys_file = path;
}
void set_keylog_file(StringView path) { m_keylog_file = path; }
Optional<ByteString> keylog_file() const { return m_keylog_file; }
ErrorOr<Vector<TypedBlob>> get_authorized_keys_for_user() const;
private:
@@ -43,6 +46,7 @@ private:
bool m_use_unsafe_stubbed_private_key { false };
ByteString m_user_authorized_keys_file;
Optional<ByteString> m_keylog_file;
};
}

View File

@@ -57,10 +57,12 @@ ErrorOr<int> serenity_main(Main::Arguments args)
Optional<u32> port {};
bool unsafe_stub_private_key { false };
Optional<StringView> user_authorized_keys_file {};
Optional<StringView> keylog_file {};
Core::ArgsParser parser;
parser.add_option(port, "Port to listen on", "port", 'p', "port");
parser.add_option(user_authorized_keys_file, "File to read the user's authorized keys from", "user-authorized-keys-file", 0, "FILE");
parser.add_option(keylog_file, "File to log the connections keys to - UNSAFE", "unsafe-keylog-file", 0, "FILE");
parser.add_option(unsafe_stub_private_key, "Stub the server's private key - UNSAFE", "unsafe-stub-private-key");
parser.parse(args);
@@ -74,6 +76,9 @@ ErrorOr<int> serenity_main(Main::Arguments args)
if (user_authorized_keys_file.has_value())
SSH::Server::ServerConfiguration::the().set_user_authorized_keys_file(*user_authorized_keys_file);
if (keylog_file.has_value())
SSH::Server::ServerConfiguration::the().set_keylog_file(*keylog_file);
Core::EventLoop loop;
g_tcp_server = TRY(Core::TCPServer::try_create());