From 23f32ba39ebe5e9bbdfc4dd00d9914c0f0447ef4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 18 May 2014 20:23:08 +0200 Subject: Implement TLS support using Botan For now, it tries two TLS ports and then connects to the non-tls port. In the future we would like the user to be able to configure that. fix #2435 --- src/network/socket_handler.cpp | 172 +++++++++++++++++++++++++++++++++---- src/network/socket_handler.hpp | 186 ++++++++++++++++++++++++++++++++++------- 2 files changed, 310 insertions(+), 48 deletions(-) (limited to 'src/network') diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index d509513..d989623 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -10,26 +10,39 @@ #include #include #include +#include #include #include -#include #include #include -using namespace std::string_literals; +#ifdef BOTAN_FOUND +# include +#endif #ifndef UIO_FASTIOV # define UIO_FASTIOV 8 #endif +using namespace std::string_literals; + +namespace ph = std::placeholders; + SocketHandler::SocketHandler(std::shared_ptr poller): socket(-1), poller(poller), + use_tls(false), connected(false), connecting(false) -{ -} +#ifdef BOTAN_FOUND + , + rng(), + credential_manager(), + policy(), + session_manager(rng) +#endif +{} void SocketHandler::init_socket(const struct addrinfo* rp) { @@ -47,10 +60,11 @@ void SocketHandler::init_socket(const struct addrinfo* rp) throw std::runtime_error("Could not initialize socket: "s + strerror(errno)); } -void SocketHandler::connect(const std::string& address, const std::string& port) +void SocketHandler::connect(const std::string& address, const std::string& port, const bool tls) { this->address = address; this->port = port; + this->use_tls = tls; utils::ScopeGuard sg; @@ -106,6 +120,10 @@ void SocketHandler::connect(const std::string& address, const std::string& port) this->poller->add_socket_handler(this); this->connected = true; this->connecting = false; +#ifdef BOTAN_FOUND + if (this->use_tls) + this->start_tls(); +#endif this->on_connected(); return ; } @@ -133,10 +151,20 @@ void SocketHandler::connect(const std::string& address, const std::string& port) void SocketHandler::connect() { - this->connect(this->address, this->port); + this->connect(this->address, this->port, this->use_tls); } void SocketHandler::on_recv() +{ +#ifdef BOTAN_FOUND + if (this->use_tls) + this->tls_recv(); + else +#endif + this->plain_recv(); +} + +void SocketHandler::plain_recv() { static constexpr size_t buf_size = 4096; char buf[buf_size]; @@ -145,6 +173,23 @@ void SocketHandler::on_recv() if (recv_buf == nullptr) recv_buf = buf; + const ssize_t size = this->do_recv(recv_buf, buf_size); + + if (size > 0) + { + if (buf == recv_buf) + { + // data needs to be placed in the in_buf string, because no buffer + // was provided to receive that data directly. The in_buf buffer + // will be handled in parse_in_buffer() + this->in_buf += std::string(buf, size); + } + this->parse_in_buffer(size); + } +} + +ssize_t SocketHandler::do_recv(void* recv_buf, const size_t buf_size) +{ ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0); if (0 == size) { @@ -155,22 +200,17 @@ void SocketHandler::on_recv() { log_warning("Error while reading from socket: " << strerror(errno)); if (this->connecting) - this->on_connection_failed(strerror(errno)); + { + this->close(); + this->on_connection_failed(strerror(errno)); + } else - this->on_connection_close(); - this->close(); - } - else - { - if (buf == recv_buf) { - // data needs to be placed in the in_buf string, because no buffer - // was provided to receive that data directly. The in_buf buffer - // will be handled in parse_in_buffer() - this->in_buf += std::string(buf, size); + this->close(); + this->on_connection_close(); } - this->parse_in_buffer(size); } + return size; } void SocketHandler::on_send() @@ -241,6 +281,16 @@ socket_t SocketHandler::get_socket() const } void SocketHandler::send_data(std::string&& data) +{ +#ifdef BOTAN_FOUND + if (this->use_tls) + this->tls_send(std::move(data)); + else +#endif + this->raw_send(std::move(data)); +} + +void SocketHandler::raw_send(std::string&& data) { if (data.empty()) return ; @@ -269,3 +319,89 @@ void* SocketHandler::get_receive_buffer(const size_t) const { return nullptr; } + +#ifdef BOTAN_FOUND +void SocketHandler::start_tls() +{ + Botan::TLS::Server_Information server_info(this->address, "irc", std::stoul(this->port)); + this->tls = std::make_unique( + std::bind(&SocketHandler::tls_output_fn, this, ph::_1, ph::_2), + std::bind(&SocketHandler::tls_data_cb, this, ph::_1, ph::_2), + std::bind(&SocketHandler::tls_alert_cb, this, ph::_1, ph::_2, ph::_3), + std::bind(&SocketHandler::tls_handshake_cb, this, ph::_1), + session_manager, credential_manager, policy, + rng, server_info, Botan::TLS::Protocol_Version::latest_tls_version()); +} + +void SocketHandler::tls_recv() +{ + static constexpr size_t buf_size = 4096; + char recv_buf[buf_size]; + + const ssize_t size = this->do_recv(recv_buf, buf_size); + if (size > 0) + { + const bool was_active = this->tls->is_active(); + this->tls->received_data(reinterpret_cast(recv_buf), + static_cast(size)); + if (!was_active && this->tls->is_active()) + this->on_tls_activated(); + } +} + +void SocketHandler::tls_send(std::string&& data) +{ + if (this->tls->is_active()) + { + const bool was_active = this->tls->is_active(); + if (!this->pre_buf.empty()) + { + this->tls->send(reinterpret_cast(this->pre_buf.data()), + this->pre_buf.size()); + this->pre_buf = ""; + } + if (!data.empty()) + this->tls->send(reinterpret_cast(data.data()), + data.size()); + if (!was_active && this->tls->is_active()) + this->on_tls_activated(); + } + else + this->pre_buf += data; +} + +void SocketHandler::tls_data_cb(const Botan::byte* data, size_t size) +{ + this->in_buf += std::string(reinterpret_cast(data), + size); + if (!this->in_buf.empty()) + this->parse_in_buffer(size); +} + +void SocketHandler::tls_output_fn(const Botan::byte* data, size_t size) +{ + this->raw_send(std::string(reinterpret_cast(data), size)); +} + +void SocketHandler::tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t) +{ + log_debug("tls_alert: " << alert.type_string()); +} + +bool SocketHandler::tls_handshake_cb(const Botan::TLS::Session& session) +{ + log_debug("Handshake with " << session.server_info().hostname() << " complete." + << " Version: " << session.version().to_string() + << " using " << session.ciphersuite().to_string()); + if (!session.session_id().empty()) + log_debug("Session ID " << Botan::hex_encode(session.session_id())); + if (!session.session_ticket().empty()) + log_debug("Session ticket " << Botan::hex_encode(session.session_ticket())); + return true; +} + +void SocketHandler::on_tls_activated() +{ + this->send_data(""); +} +#endif // BOTAN_FOUND diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 1be3db1..02311c4 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -1,6 +1,8 @@ #ifndef SOCKET_HANDLER_INCLUDED # define SOCKET_HANDLER_INCLUDED +#include + #include #include #include @@ -10,6 +12,26 @@ #include #include +#include "config.h" + +#ifdef BOTAN_FOUND +# include +# include + +/** + * A very simple credential manager that accepts any certificate. + */ +class Permissive_Credentials_Manager: public Botan::Credentials_Manager +{ +public: + void verify_certificate_chain(const std::string& type, const std::string& purported_hostname, const std::vector&) + { // TODO: Offer the admin to disallow connection on untrusted + // certificates + log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname); + } +}; +#endif // BOTAN_FOUND + typedef int socket_t; class Poller; @@ -24,21 +46,19 @@ class SocketHandler { protected: ~SocketHandler() {} + public: explicit SocketHandler(std::shared_ptr poller); /** - * Initialize the socket with the parameters contained in the given - * addrinfo structure. - */ - void init_socket(const struct addrinfo* rp); - /** - * Connect to the remote server, and call on_connected() if this succeeds + * Connect to the remote server, and call on_connected() if this + * succeeds. If tls is true, we set use_tls to true and will also call + * start_tls() when the connection succeeds. */ - void connect(const std::string& address, const std::string& port); + void connect(const std::string& address, const std::string& port, const bool tls); void connect(); /** - * Reads data in our in_buf and the call parse_in_buf, for the implementor - * to handle the data received so far. + * Reads raw data from the socket. And pass it to parse_in_buffer() + * If we are using TLS on this connection, we call tls_recv() */ void on_recv(); /** @@ -48,6 +68,9 @@ public: /** * Add the given data to out_buf and tell our poller that we want to be * notified when a send event is ready. + * + * This can be overriden if we want to modify the data before sending + * it. For example if we want to encrypt it. */ void send_data(std::string&& data); /** @@ -87,29 +110,94 @@ public: bool is_connected() const; bool is_connecting() const; -protected: +private: /** - * Provide a buffer in which data can be directly received. This can be - * used to avoid copying data into in_buf before using it. If no buffer - * can provided, nullptr is returned (the default implementation does - * that). + * Initialize the socket with the parameters contained in the given + * addrinfo structure. */ - virtual void* get_receive_buffer(const size_t size) const; + void init_socket(const struct addrinfo* rp); /** - * The handled socket. + * Reads from the socket into the provided buffer. If an error occurs + * (read returns <= 0), the handling of the error is done here (close the + * connection, log a message, etc). + * + * Returns the value returned by ::recv(), so the buffer should not be + * used if it’s not positive. */ - socket_t socket; + ssize_t do_recv(void* recv_buf, const size_t buf_size); /** - * Where data read from the socket is added until we can extract a full - * and meaningful “message” from it. - * - * TODO: something more efficient than a string. + * Reads data from the socket and calls parse_in_buffer with it. */ - std::string in_buf; + void plain_recv(); + /** + * Mark the given data as ready to be sent, as-is, on the socket, as soon + * as we can. + */ + void raw_send(std::string&& data); + +#ifdef BOTAN_FOUND + /** + * Create the TLS::Client object, with all the callbacks etc. This must be + * called only when we know we are able to send TLS-encrypted data over + * the socket. + */ + void start_tls(); + /** + * An additional step to pass the data into our tls object to decrypt it + * before passing it to parse_in_buffer. + */ + void tls_recv(); + /** + * Pass the data to the tls object in order to encrypt it. The tls object + * will then call raw_send as a callback whenever data as been encrypted + * and can be sent on the socket. + */ + void tls_send(std::string&& data); + /** + * Called by the tls object that some data has been decrypt. We call + * parse_in_buffer() to handle that unencrypted data. + */ + void tls_data_cb(const Botan::byte* data, size_t size); + /** + * Called by the tls object to indicate that some data has been encrypted + * and is now ready to be sent on the socket as is. + */ + void tls_output_fn(const Botan::byte* data, size_t size); + /** + * Called by the tls object to indicate that a TLS alert has been + * received. We don’t use it, we just log some message, at the moment. + */ + void tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t); + /** + * Called by the tls object at the end of the TLS handshake. We don't do + * anything here appart from logging the TLS session information. + */ + bool tls_handshake_cb(const Botan::TLS::Session& session); + /** + * Called whenever the tls session goes from inactive to active. This + * means that the handshake has just been successfully done, and we can + * now proceed to send any available data into our tls object. + */ + void on_tls_activated(); +#endif // BOTAN_FOUND + /** + * The handled socket. + */ + socket_t socket; /** * Where data is added, when we want to send something to the client. */ std::list out_buf; + /** + * Keep the details of the addrinfo the triggered a EINPROGRESS error when + * connect()ing to it, to reuse it directly when connect() is called + * again. + */ + struct addrinfo addrinfo; + struct sockaddr ai_addr; + socklen_t ai_addrlen; + +protected: /** * A pointer to the poller that manages us, because we need to communicate * with it, sometimes (for example to tell it that he now needs to watch @@ -119,6 +207,25 @@ protected: * (actually it is sharing our ownership with a Bridge). */ std::shared_ptr poller; + /** + * Where data read from the socket is added until we can extract a full + * and meaningful “message” from it. + * + * TODO: something more efficient than a string. + */ + std::string in_buf; + /** + * Whether we are using TLS on this connection or not. + */ + bool use_tls; + /** + * Provide a buffer in which data can be directly received. This can be + * used to avoid copying data into in_buf before using it. If no buffer + * needs to be provided, nullptr is returned (the default implementation + * does that), in that case our internal in_buf will be used to save the + * data until it can be used by parse_in_buffer(). + */ + virtual void* get_receive_buffer(const size_t size) const; /** * Hostname we are connected/connecting to */ @@ -127,14 +234,6 @@ protected: * Port we are connected/connecting to */ std::string port; - /** - * Keep the details of the addrinfo the triggered a EINPROGRESS error when - * connect()ing to it, to reuse it directly when connect() is called - * again. - */ - struct addrinfo addrinfo; - struct sockaddr ai_addr; - socklen_t ai_addrlen; bool connected; bool connecting; @@ -144,6 +243,33 @@ private: SocketHandler(SocketHandler&&) = delete; SocketHandler& operator=(const SocketHandler&) = delete; SocketHandler& operator=(SocketHandler&&) = delete; + +#ifdef BOTAN_FOUND + /** + * Botan stuff to manipulate a TLS session. + */ + Botan::AutoSeeded_RNG rng; + Permissive_Credentials_Manager credential_manager; + Botan::TLS::Policy policy; + Botan::TLS::Session_Manager_In_Memory session_manager; + /** + * We use a unique_ptr because we may not want to create the object at + * all. The Botan::TLS::Client object generates a handshake message as + * soon and calls the output_fn callback with it as soon as it is + * created. Therefore, we do not want to create it if do not intend to do + * send any TLS-encrypted message. We create the object only when needed + * (for example after we have negociated a TLS session using a STARTTLS + * message, or stuf like that). + * + * See start_tls for the method where this object is created. + */ + std::unique_ptr tls; + /** + * An additional buffer to keep data that the user wants to send, but + * cannot because the handshake is not done. + */ + std::string pre_buf; +#endif // BOTAN_FOUND }; #endif // SOCKET_HANDLER_INCLUDED -- cgit v1.2.3