Skip to content

Commit

Permalink
core: implement SSL session resumption for data connections
Browse files Browse the repository at this point in the history
  • Loading branch information
deniskovalchuk committed Apr 20, 2024
1 parent 397a126 commit 5a8364a
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 14 deletions.
2 changes: 2 additions & 0 deletions include/ftp/client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ class client

detail::data_connection_ptr create_data_connection(std::string_view command, replies & replies);

void ssl_handshake_data_connection(detail::data_connection & connection, ssl::context & ssl_context);

detail::data_connection_ptr process_epsv_command(std::string_view command, replies & replies);

detail::data_connection_ptr process_eprt_command(std::string_view command, replies & replies);
Expand Down
2 changes: 2 additions & 0 deletions include/ftp/detail/control_connection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ class control_connection

void ssl_shutdown();

SSL_SESSION * get_ssl_session();

void send(std::string_view command);

reply recv();
Expand Down
2 changes: 1 addition & 1 deletion include/ftp/detail/data_connection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class data_connection

void accept();

void set_ssl(boost::asio::ssl::context *ssl_context);
void set_ssl(boost::asio::ssl::context *ssl_context, SSL_SESSION *ssl_session = nullptr);

void ssl_handshake();

Expand Down
2 changes: 2 additions & 0 deletions include/ftp/detail/socket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class socket : public socket_base

void ssl_shutdown(boost::system::error_code & ec) override;

SSL_SESSION * get_ssl_session() override;

std::size_t write(const char *buf, std::size_t size, boost::system::error_code & ec) override;

std::size_t write(std::string_view buf, boost::system::error_code & ec) override;
Expand Down
3 changes: 3 additions & 0 deletions include/ftp/detail/socket_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <boost/asio/write.hpp>
#include <boost/asio/read_until.hpp>
#include <boost/asio/ssl/stream_base.hpp>
#include <openssl/ssl.h>
#include <memory>

namespace ftp::detail
Expand All @@ -49,6 +50,8 @@ class socket_base

virtual void ssl_shutdown(boost::system::error_code & ec) = 0;

virtual SSL_SESSION * get_ssl_session() = 0;

virtual std::size_t write(const char *buf, std::size_t size, boost::system::error_code & ec) = 0;

virtual std::size_t write(std::string_view buf, boost::system::error_code & ec) = 0;
Expand Down
6 changes: 5 additions & 1 deletion include/ftp/detail/ssl_socket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ namespace ftp::detail
class ssl_socket : public socket_base
{
public:
ssl_socket(boost::asio::ip::tcp::socket && socket, boost::asio::ssl::context & ssl_context);
ssl_socket(boost::asio::ip::tcp::socket && socket,
boost::asio::ssl::context & ssl_context,
SSL_SESSION *ssl_session = nullptr);

void connect(const boost::asio::ip::tcp::resolver::results_type & eps, boost::system::error_code & ec) override;

Expand All @@ -51,6 +53,8 @@ class ssl_socket : public socket_base

void ssl_shutdown(boost::system::error_code & ec) override;

SSL_SESSION * get_ssl_session() override;

std::size_t write(const char *buf, std::size_t size, boost::system::error_code & ec) override;

std::size_t write(std::string_view buf, boost::system::error_code & ec) override;
Expand Down
31 changes: 23 additions & 8 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,25 @@ data_connection_ptr client::create_data_connection(std::string_view command, rep
}
}

void client::ssl_handshake_data_connection(data_connection & connection, ssl::context & ssl_context)
{
SSL_SESSION *ssl_session;

long cache_mode = SSL_CTX_get_session_cache_mode(ssl_context.native_handle());
if (cache_mode & SSL_SESS_CACHE_CLIENT)
{
/* Reuse the control connection SSL session. */
ssl_session = control_connection_.get_ssl_session();
}
else
{
ssl_session = nullptr;
}

connection.set_ssl(&ssl_context, ssl_session);
connection.ssl_handshake();
}

data_connection_ptr client::process_epsv_command(std::string_view command, replies & replies)
{
/* Process the EPSV command. */
Expand Down Expand Up @@ -708,8 +727,7 @@ data_connection_ptr client::process_epsv_command(std::string_view command, repli

if (ssl_context_)
{
connection->set_ssl(ssl_context_.get());
connection->ssl_handshake();
ssl_handshake_data_connection(*connection, *ssl_context_);
}

/* The data connection is ready for data transfer. */
Expand Down Expand Up @@ -789,8 +807,7 @@ data_connection_ptr client::process_eprt_command(std::string_view command, repli

if (ssl_context_)
{
connection->set_ssl(ssl_context_.get());
connection->ssl_handshake();
ssl_handshake_data_connection(*connection, *ssl_context_);
}

/* The data connection is ready for data transfer. */
Expand Down Expand Up @@ -860,8 +877,7 @@ data_connection_ptr client::process_pasv_command(std::string_view command, repli

if (ssl_context_)
{
connection->set_ssl(ssl_context_.get());
connection->ssl_handshake();
ssl_handshake_data_connection(*connection, *ssl_context_);
}

/* The data connection is ready for data transfer. */
Expand Down Expand Up @@ -970,8 +986,7 @@ data_connection_ptr client::process_port_command(std::string_view command, repli

if (ssl_context_)
{
connection->set_ssl(ssl_context_.get());
connection->ssl_handshake();
ssl_handshake_data_connection(*connection, *ssl_context_);
}

/* The data connection is ready for data transfer. */
Expand Down
5 changes: 5 additions & 0 deletions src/control_connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ void control_connection::ssl_shutdown()
}
}

SSL_SESSION * control_connection::get_ssl_session()
{
return socket_->get_ssl_session();
}

reply control_connection::recv()
{
std::string status_string;
Expand Down
4 changes: 2 additions & 2 deletions src/data_connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,13 @@ void data_connection::accept()
}
}

void data_connection::set_ssl(boost::asio::ssl::context *ssl_context)
void data_connection::set_ssl(boost::asio::ssl::context *ssl_context, SSL_SESSION *ssl_session)
{
boost::asio::ip::tcp::socket raw = socket_->detach();

if (ssl_context)
{
socket_ = std::make_unique<ssl_socket>(std::move(raw), *ssl_context);
socket_ = std::make_unique<ssl_socket>(std::move(raw), *ssl_context, ssl_session);
}
else
{
Expand Down
6 changes: 6 additions & 0 deletions src/socket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ void socket::ssl_shutdown(boost::system::error_code & ec)
/* Make sense only for SSL-sockets. */
}

SSL_SESSION * socket::get_ssl_session()
{
/* Make sense only for SSL-sockets. */
return nullptr;
}

std::size_t socket::write(const char *buf, std::size_t size, boost::system::error_code & ec)
{
return socket_base::write(socket_, buf, size, ec);
Expand Down
16 changes: 14 additions & 2 deletions src/ssl_socket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,16 @@
namespace ftp::detail
{

ssl_socket::ssl_socket(boost::asio::ip::tcp::socket && socket, boost::asio::ssl::context & ssl_context)
ssl_socket::ssl_socket(boost::asio::ip::tcp::socket && socket,
boost::asio::ssl::context & ssl_context,
SSL_SESSION *ssl_session)
: socket_(std::move(socket), ssl_context)
{}
{
if (ssl_session)
{
SSL_set_session(socket_.native_handle(), ssl_session);
}
}

void ssl_socket::connect(const boost::asio::ip::tcp::resolver::results_type & eps, boost::system::error_code & ec)
{
Expand Down Expand Up @@ -62,6 +69,11 @@ void ssl_socket::ssl_shutdown(boost::system::error_code & ec)
socket_.shutdown(ec);
}

SSL_SESSION * ssl_socket::get_ssl_session()
{
return SSL_get0_session(socket_.native_handle());
}

std::size_t ssl_socket::write(const char *buf, std::size_t size, boost::system::error_code & ec)
{
return socket_base::write(socket_, buf, size, ec);
Expand Down
40 changes: 40 additions & 0 deletions test/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1386,4 +1386,44 @@ TEST_P(ssl_client_parameterized, upload_download_file)
check_reply(client.disconnect(), "221 Goodbye.");
}

class ssl_client_with_transfer_mode : public ssl_client,
public testing::WithParamInterface<ftp::transfer_mode>
{
};

INSTANTIATE_TEST_SUITE_P(all_modes, ssl_client_with_transfer_mode, testing::Values(ftp::transfer_mode::active,
ftp::transfer_mode::passive));

TEST_P(ssl_client_with_transfer_mode, ssl_session_resumption)
{
ftp::transfer_mode mode = GetParam();
ftp::ssl::context_ptr ssl_context = std::make_unique<ftp::ssl::context>(ftp::ssl::context::tlsv13_client);
ssl_context->load_verify_file(ftp::test::server::get_root_ca_cert_path().string());
ssl_context->load_verify_file(ftp::test::server::get_ca_cert_path().string());
ssl_context->set_verify_mode(boost::asio::ssl::verify_peer);

/* Set SSL_SESS_CACHE_CLIENT to use a single SSL session for control and data connections. */
SSL_CTX_set_session_cache_mode(ssl_context->native_handle(), SSL_SESS_CACHE_CLIENT);

ftp::client client(mode, ftp::transfer_type::ascii, std::move(ssl_context));

for (int i = 0; i < 3; i++)
{
check_reply(client.connect("127.0.0.1", 2142, "user", "password"), CRLF("220 FTP server is ready.",
"234 AUTH TLS successful.",
"331 Username ok, send password.",
"230 Login successful.",
"200 PBSZ=0 successful.",
"200 Protection set to Private",
"200 Type set to: ASCII."));

for (int j = 0; j < 3; j++)
{
check_last_reply(client.get_file_list("."), "226 Transfer complete.");
}

check_reply(client.disconnect(), "221 Goodbye.");
}
}

} // namespace

0 comments on commit 5a8364a

Please sign in to comment.