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
This commit is contained in:
Florent Le Coz
2014-06-08 05:07:54 +02:00
parent fa07130991
commit 23f32ba39e
8 changed files with 392 additions and 64 deletions
+158 -22
View File
@@ -10,26 +10,39 @@
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <cstring>
#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <iostream>
using namespace std::string_literals;
#ifdef BOTAN_FOUND
# include <botan/hex.h>
#endif
#ifndef UIO_FASTIOV
# define UIO_FASTIOV 8
#endif
using namespace std::string_literals;
namespace ph = std::placeholders;
SocketHandler::SocketHandler(std::shared_ptr<Poller> 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,22 +173,9 @@ void SocketHandler::on_recv()
if (recv_buf == nullptr)
recv_buf = buf;
ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0);
if (0 == size)
{
this->on_connection_close();
this->close();
}
else if (-1 == size)
{
log_warning("Error while reading from socket: " << strerror(errno));
if (this->connecting)
this->on_connection_failed(strerror(errno));
else
this->on_connection_close();
this->close();
}
else
const ssize_t size = this->do_recv(recv_buf, buf_size);
if (size > 0)
{
if (buf == recv_buf)
{
@@ -173,6 +188,31 @@ void SocketHandler::on_recv()
}
}
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)
{
this->on_connection_close();
this->close();
}
else if (-1 == size)
{
log_warning("Error while reading from socket: " << strerror(errno));
if (this->connecting)
{
this->close();
this->on_connection_failed(strerror(errno));
}
else
{
this->close();
this->on_connection_close();
}
}
return size;
}
void SocketHandler::on_send()
{
struct iovec msg_iov[UIO_FASTIOV] = {};
@@ -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<Botan::TLS::Client>(
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<const Botan::byte*>(recv_buf),
static_cast<size_t>(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<const Botan::byte*>(this->pre_buf.data()),
this->pre_buf.size());
this->pre_buf = "";
}
if (!data.empty())
this->tls->send(reinterpret_cast<const Botan::byte*>(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<const char*>(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<const char*>(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
+156 -30
View File
@@ -1,6 +1,8 @@
#ifndef SOCKET_HANDLER_INCLUDED
# define SOCKET_HANDLER_INCLUDED
#include <logger/logger.hpp>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
@@ -10,6 +12,26 @@
#include <string>
#include <list>
#include "config.h"
#ifdef BOTAN_FOUND
# include <botan/botan.h>
# include <botan/tls_client.h>
/**
* 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<Botan::X509_Certificate>&)
{ // 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> poller);
/**
* Initialize the socket with the parameters contained in the given
* addrinfo structure.
* 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 init_socket(const struct addrinfo* rp);
/**
* Connect to the remote server, and call on_connected() if this 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);
/**
* 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.
*/
ssize_t do_recv(void* recv_buf, const size_t buf_size);
/**
* Reads data from the socket and calls parse_in_buffer with it.
*/
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 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;
/**
* Where data is added, when we want to send something to the client.
*/
std::list<std::string> 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> 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<Botan::TLS::Client> 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