diff options
-rw-r--r-- | doc/manual/tls.rst | 10 | ||||
-rw-r--r-- | doc/relnotes/1_11_13.rst | 5 | ||||
-rw-r--r-- | src/cmd/tls_proxy.cpp | 592 | ||||
-rw-r--r-- | src/cmd/tls_server_asio.cpp | 326 |
4 files changed, 599 insertions, 334 deletions
diff --git a/doc/manual/tls.rst b/doc/manual/tls.rst index b3ec1c0ea..1b7929f1b 100644 --- a/doc/manual/tls.rst +++ b/doc/manual/tls.rst @@ -269,9 +269,7 @@ TLS Clients resized as needed to process inputs). Otherwise some reasonable default is used. -A simple TLS client example: - -.. literalinclude:: ../../src/cmd/tls_client.cpp +A TLS client example using BSD sockets is in `src/cmd/tls_client.cpp` TLS Servers ---------------------------------------- @@ -309,10 +307,8 @@ not until they actually receive a hello without this parameter. renegotiation, but might change across different connections using that session. -An example TLS server that can handle concurrent connections using -asio follows: - -.. literalinclude:: ../../src/cmd/tls_server_asio.cpp +An example TLS server implementation using asio is available in +`src/cmd/tls_proxy.cpp`. .. _tls_sessions: diff --git a/doc/relnotes/1_11_13.rst b/doc/relnotes/1_11_13.rst index 8a9ed5872..d0ca04245 100644 --- a/doc/relnotes/1_11_13.rst +++ b/doc/relnotes/1_11_13.rst @@ -1,6 +1,9 @@ Version 1.11.13, Not Yet Released ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +* The command line tool now has `tls_proxy` which negotiates TLS with + clients and forwards the plaintext to a specified port. + * Add MCEIES, a McEliece-based integrated encryption system using AES-256 in OCB mode for message encryption/authentication. @@ -9,7 +12,7 @@ Version 1.11.13, Not Yet Released * Add SHA-512/256 * The format of serialized TLS sessions has changed. Additiionally, PEM - formatted sessions now use the label of "TLS SESSION" instead of "SSL SESSION". + formatted sessions now use the label of "TLS SESSION" instead of "SSL SESSION" * Serialized TLS sessions are now encrypted using AES-256/GCM instead of a CBC+HMAC construction. diff --git a/src/cmd/tls_proxy.cpp b/src/cmd/tls_proxy.cpp new file mode 100644 index 000000000..867e2280e --- /dev/null +++ b/src/cmd/tls_proxy.cpp @@ -0,0 +1,592 @@ +/* +* TLS Server Proxy +* (C) 2014,2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "apps.h" + +#if defined(BOTAN_HAS_TLS) + +#include <iostream> +#include <string> +#include <vector> +#include <thread> + +#define _GLIBCXX_HAVE_GTHR_DEFAULT +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include <botan/tls_server.h> +#include <botan/x509cert.h> +#include <botan/pkcs8.h> +#include <botan/auto_rng.h> + +#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) + #include <botan/tls_session_manager_sqlite.h> +#endif + +using boost::asio::ip::tcp; + +namespace Botan { + +namespace { + +void log_exception(const char* where, const std::exception& e) + { + std::cout << where << ' ' << e.what() << std::endl; + } + +void log_error(const char* where, const boost::system::error_code& error) + { + std::cout << where << ' ' << error.message() << std::endl; + } + +void log_binary_message(const char* where, const byte buf[], size_t buf_len) + { + //std::cout << where << ' ' << hex_encode(buf, buf_len) << std::endl; + } + +void log_text_message(const char* where, const byte buf[], size_t buf_len) + { + //const char* c = reinterpret_cast<const char*>(buf); + //std::cout << where << ' ' << std::string(c, c + buf_len) << std::endl; + } + +class tls_proxy_session : public boost::enable_shared_from_this<tls_proxy_session> + { + public: + enum { readbuf_size = 4 * 1024 }; + + typedef boost::shared_ptr<tls_proxy_session> pointer; + + static pointer create(boost::asio::io_service& io, + TLS::Session_Manager& session_manager, + Credentials_Manager& credentials, + TLS::Policy& policy, + RandomNumberGenerator& rng, + tcp::resolver::iterator endpoints) + { + return pointer( + new tls_proxy_session( + io, + session_manager, + credentials, + policy, + rng, + endpoints) + ); + } + + tcp::socket& client_socket() { return m_client_socket; } + + void start() + { + m_c2p.resize(readbuf_size); + + client_read(boost::system::error_code(), 0); // start read loop + } + + void stop() { m_client_socket.close(); } + + private: + tls_proxy_session(boost::asio::io_service& io, + TLS::Session_Manager& session_manager, + Credentials_Manager& credentials, + TLS::Policy& policy, + RandomNumberGenerator& rng, + tcp::resolver::iterator endpoints) : + m_strand(io), + m_server_endpoints(endpoints), + m_client_socket(io), + m_server_socket(io), + m_tls(boost::bind(&tls_proxy_session::tls_proxy_write_to_client, this, _1, _2), + boost::bind(&tls_proxy_session::tls_client_write_to_proxy, this, _1, _2), + boost::bind(&tls_proxy_session::tls_alert_cb, this, _1, _2, _3), + boost::bind(&tls_proxy_session::tls_handshake_complete, this, _1), + session_manager, + credentials, + policy, + rng) + { + } + + void client_read(const boost::system::error_code& error, + size_t bytes_transferred) + { + if(error) + { + log_error("Read failed", error); + stop(); + return; + } + + try + { + if(!m_tls.is_active()) + log_binary_message("From client", &m_c2p[0], bytes_transferred); + m_tls.received_data(&m_c2p[0], bytes_transferred); + } + catch(std::exception& e) + { + log_exception("TLS connection failed", e); + stop(); + return; + } + + m_client_socket.async_read_some( + boost::asio::buffer(&m_c2p[0], m_c2p.size()), + m_strand.wrap(boost::bind(&tls_proxy_session::client_read, shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + + void handle_client_write_completion(const boost::system::error_code& error) + { + if(error) + { + log_error("Client write", error); + stop(); + return; + } + + m_p2c.clear(); + tls_proxy_write_to_client(nullptr, 0); // initiate another write if needed + } + + void handle_server_write_completion(const boost::system::error_code& error) + { + if(error) + { + log_error("Server write", error); + stop(); + return; + } + + m_p2s.clear(); + tls_proxy_write_to_server(nullptr, 0); // initiate another write if needed + } + + void tls_client_write_to_proxy(const byte buf[], size_t buf_len) + { + tls_proxy_write_to_server(buf, buf_len); + } + + void tls_proxy_write_to_client(const byte buf[], size_t buf_len) + { + if(buf_len > 0) + m_p2c_pending.insert(m_p2c_pending.end(), buf, buf + buf_len); + + // no write now active and we still have output pending + if(m_p2c.empty() && !m_p2c_pending.empty()) + { + std::swap(m_p2c_pending, m_p2c); + + //log_binary_message("To Client", &m_p2c[0], m_p2c.size()); + + boost::asio::async_write( + m_client_socket, + boost::asio::buffer(&m_p2c[0], m_p2c.size()), + m_strand.wrap(boost::bind( + &tls_proxy_session::handle_client_write_completion, + shared_from_this(), + boost::asio::placeholders::error))); + } + } + + void tls_proxy_write_to_server(const byte buf[], size_t buf_len) + { + if(buf_len > 0) + m_p2s_pending.insert(m_p2s_pending.end(), buf, buf + buf_len); + + // no write now active and we still have output pending + if(m_p2s.empty() && !m_p2s_pending.empty()) + { + std::swap(m_p2s_pending, m_p2s); + + log_text_message("To Server", &m_p2s[0], m_p2s.size()); + + boost::asio::async_write( + m_server_socket, + boost::asio::buffer(&m_p2s[0], m_p2s.size()), + m_strand.wrap(boost::bind( + &tls_proxy_session::handle_server_write_completion, + shared_from_this(), + boost::asio::placeholders::error))); + } + } + + void server_read(const boost::system::error_code& error, + size_t bytes_transferred) + { + if(error) + { + log_error("Server read failed", error); + stop(); + return; + } + + try + { + if(bytes_transferred) + { + log_text_message("Server to client", &m_s2p[0], m_s2p.size()); + log_binary_message("Server to client", &m_s2p[0], m_s2p.size()); + m_tls.send(&m_s2p[0], bytes_transferred); + } + } + catch(std::exception& e) + { + log_exception("TLS connection failed", e); + stop(); + return; + } + + m_s2p.resize(readbuf_size); + + m_server_socket.async_read_some( + boost::asio::buffer(&m_s2p[0], m_s2p.size()), + m_strand.wrap(boost::bind(&tls_proxy_session::server_read, shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + + bool tls_handshake_complete(const TLS::Session& session) + { + //std::cout << "Handshake from client complete\n"; + + m_hostname = session.server_info().hostname(); + + if(m_hostname != "") + std::cout << "Client requested hostname '" << m_hostname << "'\n"; + + async_connect(m_server_socket, m_server_endpoints, + [this](boost::system::error_code ec, tcp::resolver::iterator endpoint) + { + if(ec) + { + log_error("Server connection", ec); + return; + } + //std::cout << "Connected to " << endpoint->host_name() << ' ' + //<< endpoint->service_name() << "\n"; + tls_proxy_write_to_server(nullptr, 0); + + server_read(boost::system::error_code(), 0); // start read loop + }); + return true; + } + + void tls_alert_cb(TLS::Alert alert, const byte[], size_t) + { + if(alert.type() == TLS::Alert::CLOSE_NOTIFY) + { + m_tls.close(); + return; + } + else + std::cout << "Alert " << alert.type_string() << "\n"; + } + + boost::asio::io_service::strand m_strand; + + tcp::resolver::iterator m_server_endpoints; + + tcp::socket m_client_socket; + tcp::socket m_server_socket; + + TLS::Server m_tls; + std::string m_hostname; + + std::vector<byte> m_c2p; + std::vector<byte> m_p2c; + std::vector<byte> m_p2c_pending; + + std::vector<byte> m_s2p; + std::vector<byte> m_p2s; + std::vector<byte> m_p2s_pending; + }; + +class tls_proxy_server + { + public: + typedef tls_proxy_session session; + + tls_proxy_server(boost::asio::io_service& io, unsigned short port, + tcp::resolver::iterator endpoints, + Credentials_Manager& creds, + TLS::Policy& policy, + TLS::Session_Manager& session_mgr, + RandomNumberGenerator& rng) : + m_acceptor(io, tcp::endpoint(tcp::v4(), port)), + m_server_endpoints(endpoints), + m_creds(creds), + m_policy(policy), + m_session_manager(session_mgr), + m_rng(rng) + { + session::pointer new_session = make_session(); + + m_acceptor.async_accept( + new_session->client_socket(), + boost::bind( + &tls_proxy_server::handle_accept, + this, + new_session, + boost::asio::placeholders::error) + ); + } + + private: + session::pointer make_session() + { + return session::create( + m_acceptor.get_io_service(), + m_session_manager, + m_creds, + m_policy, + m_rng, + m_server_endpoints + ); + } + + void handle_accept(session::pointer new_session, + const boost::system::error_code& error) + { + if (!error) + { + new_session->start(); + + new_session = make_session(); + + m_acceptor.async_accept( + new_session->client_socket(), + boost::bind( + &tls_proxy_server::handle_accept, + this, + new_session, + boost::asio::placeholders::error) + ); + } + } + + tcp::acceptor m_acceptor; + tcp::resolver::iterator m_server_endpoints; + + Credentials_Manager& m_creds; + TLS::Policy& m_policy; + TLS::Session_Manager& m_session_manager; + RandomNumberGenerator& m_rng; + }; + +size_t choose_thread_count() + { + size_t result = std::thread::hardware_concurrency(); + + if(result) + return result; + + return 2; + } + +class Proxy_Credentials_Manager : public Credentials_Manager + { + public: + Proxy_Credentials_Manager(RandomNumberGenerator& rng, + const std::string& server_crt, + const std::string& server_key) + { + try + { + Certificate_Info cert; + + + try + { + cert.key.reset(PKCS8::load_key(server_key, rng)); + } + catch(std::exception& e) + { + log_exception("Loading server key", e); + throw; // fatal + } + + try + { + DataSource_Stream in(server_crt); + while(!in.end_of_data()) + cert.certs.push_back(X509_Certificate(in)); + } + catch(std::exception& e) + { + log_exception("Loading certs", e); + } + + // TODO: attempt to validate chain ourselves + + m_creds.push_back(cert); + } + catch(std::exception& e) + { + log_exception("Loading server key", e); + throw; // fatal + } + + try + { + // TODO: make path configurable + std::shared_ptr<Certificate_Store> cs(new Certificate_Store_In_Memory("/usr/share/ca-certificates")); + m_certstores.push_back(cs); + } + catch(std::exception& e) + { + log_exception("Loading CA certs", e); + //throw; // not fatal + } + } + + std::vector<Botan::Certificate_Store*> + trusted_certificate_authorities(const std::string& type, + const std::string& /*hostname*/) + { + std::vector<Botan::Certificate_Store*> v; + + // don't ask for client certs + if(type == "tls-server") + return v; + + for(auto&& cs : m_certstores) + v.push_back(cs.get()); + + return v; + } + + void verify_certificate_chain( + const std::string& type, + const std::string& purported_hostname, + const std::vector<X509_Certificate>& cert_chain) + { + try + { + Credentials_Manager::verify_certificate_chain(type, + purported_hostname, + cert_chain); + } + catch(std::exception& e) + { + std::cout << "Certificate validation failure: " << e.what() << "\n"; + throw; + } + } + + std::vector<X509_Certificate> cert_chain( + const std::vector<std::string>& algos, + const std::string& type, + const std::string& hostname) + { + for(auto&& i : m_creds) + { + if(std::find(algos.begin(), algos.end(), i.key->algo_name()) == algos.end()) + continue; + + if(hostname != "" && !i.certs[0].matches_dns_name(hostname)) + continue; + + return i.certs; + } + + return std::vector<X509_Certificate>(); + } + + Private_Key* private_key_for(const X509_Certificate& cert, + const std::string& /*type*/, + const std::string& /*context*/) + { + for(auto&& i : m_creds) + { + if(cert == i.certs[0]) + return i.key.get(); + } + + return nullptr; + } + + private: + struct Certificate_Info + { + std::vector<X509_Certificate> certs; + std::shared_ptr<Private_Key> key; + }; + + std::vector<Certificate_Info> m_creds; + std::vector<std::shared_ptr<Certificate_Store>> m_certstores; + }; + +int tls_proxy(int argc, char* argv[]) + { + if(argc != 6) + { + std::cout << "Usage: " << argv[0] << " listen_port target_host target_port server_cert server_key\n"; + return 1; + } + + const size_t listen_port = to_u32bit(argv[1]); + const std::string target = argv[2]; + const std::string target_port = argv[3]; + + const std::string server_crt = argv[4]; + const std::string server_key = argv[5]; + + const size_t num_threads = choose_thread_count(); // make configurable + + AutoSeeded_RNG rng; + Proxy_Credentials_Manager creds(rng, server_crt, server_key); + + TLS::Policy policy; // TODO: Read policy from text file + + try + { + boost::asio::io_service io; + + tcp::resolver resolver(io); + auto server_endpoint_iterator = resolver.resolve({ target, target_port }); + +#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) + // Todo: make configurable + const std::string sessions_passphrase = "correct horse battery staple"; + const std::string sessions_db = "sessions.db"; + TLS::Session_Manager_SQLite sessions(sessions_passphrase, rng, sessions_db); +#else + TLS::Session_Manager_In_Memory sessions(rng); +#endif + + tls_proxy_server server(io, listen_port, server_endpoint_iterator, creds, policy, sessions, rng); + + std::vector<std::shared_ptr<std::thread>> threads; + + for(size_t i = 2; i <= num_threads; ++i) + threads.push_back(std::make_shared<std::thread>([&io]() { io.run(); })); + + io.run(); + + for (size_t i = 0; i < threads.size(); ++i) + threads[i]->join(); + } + catch (std::exception& e) + { + std::cerr << e.what() << std::endl; + } + + return 0; + } + +} + +} + +REGISTER_APP(tls_proxy); + +#endif diff --git a/src/cmd/tls_server_asio.cpp b/src/cmd/tls_server_asio.cpp deleted file mode 100644 index 9d31107bb..000000000 --- a/src/cmd/tls_server_asio.cpp +++ /dev/null @@ -1,326 +0,0 @@ -/* -* (C) 2014 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_TLS) -#include <iostream> -#include <string> -#include <vector> -#include <thread> -#define _GLIBCXX_HAVE_GTHR_DEFAULT -#include <boost/asio.hpp> -#include <boost/bind.hpp> -//#include <boost/thread.hpp> -#include <boost/shared_ptr.hpp> -#include <boost/enable_shared_from_this.hpp> - -#include <botan/tls_server.h> -#include <botan/x509cert.h> -#include <botan/pkcs8.h> -#include <botan/auto_rng.h> - -#include "credentials.h" - -using Botan::byte; -using boost::asio::ip::tcp; - -namespace { - -class tls_server_session : public boost::enable_shared_from_this<tls_server_session> - { - public: - typedef boost::shared_ptr<tls_server_session> pointer; - - static pointer create(boost::asio::io_service& io_service, - Botan::TLS::Session_Manager& session_manager, - Botan::Credentials_Manager& credentials, - Botan::TLS::Policy& policy, - Botan::RandomNumberGenerator& rng) - { - return pointer( - new tls_server_session( - io_service, - session_manager, - credentials, - policy, - rng) - ); - } - - tcp::socket& socket() { return m_socket; } - - void start() - { - m_socket.async_read_some( - boost::asio::buffer(m_read_buf, sizeof(m_read_buf)), - m_strand.wrap( - boost::bind(&tls_server_session::handle_read, shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred))); - } - - void stop() { m_socket.close(); } - - private: - tls_server_session(boost::asio::io_service& io_service, - Botan::TLS::Session_Manager& session_manager, - Botan::Credentials_Manager& credentials, - Botan::TLS::Policy& policy, - Botan::RandomNumberGenerator& rng) : - m_strand(io_service), - m_socket(io_service), - m_tls(boost::bind(&tls_server_session::tls_output_wanted, this, _1, _2), - boost::bind(&tls_server_session::tls_data_recv, this, _1, _2), - boost::bind(&tls_server_session::tls_alert_cb, this, _1, _2, _3), - boost::bind(&tls_server_session::tls_handshake_complete, this, _1), - session_manager, - credentials, - policy, - rng) - { - } - - void handle_read(const boost::system::error_code& error, - size_t bytes_transferred) - { - if(!error) - { - try - { - m_tls.received_data(m_read_buf, bytes_transferred); - } - catch(std::exception& e) - { - std::cout << "Read failed " << e.what() << "\n"; - stop(); - return; - } - - m_socket.async_read_some( - boost::asio::buffer(m_read_buf, sizeof(m_read_buf)), - m_strand.wrap(boost::bind(&tls_server_session::handle_read, shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred))); - } - else - { - stop(); - } - } - - void handle_write(const boost::system::error_code& error) - { - if(!error) - { - m_write_buf.clear(); - - // initiate another write if needed - tls_output_wanted(nullptr, 0); - } - else - { - stop(); - } - } - - void tls_output_wanted(const byte buf[], size_t buf_len) - { - if(buf_len > 0) - m_outbox.insert(m_outbox.end(), buf, buf + buf_len); - - // no write pending and have output pending - if(m_write_buf.empty() && !m_outbox.empty()) - { - std::swap(m_outbox, m_write_buf); - - boost::asio::async_write(m_socket, - boost::asio::buffer(&m_write_buf[0], m_write_buf.size()), - m_strand.wrap( - boost::bind(&tls_server_session::handle_write, - shared_from_this(), - boost::asio::placeholders::error))); - } - } - - void tls_alert_cb(Botan::TLS::Alert alert, const byte[], size_t) - { - if(alert.type() == Botan::TLS::Alert::CLOSE_NOTIFY) - { - m_tls.close(); - return; - } - } - - void tls_data_recv(const byte buf[], size_t buf_len) - { - m_client_data.insert(m_client_data.end(), buf, buf + buf_len); - - if(ready_to_respond()) - write_response(); - } - - bool ready_to_respond() - { - return true; // parse headers? - } - - void write_response() - { - std::string out; - out += "\r\n"; - out += "HTTP/1.0 200 OK\r\n"; - out += "Server: Botan ASIO test server\r\n"; - if(m_hostname != "") - out += "Host: " + m_hostname + "\r\n"; - out += "Content-Type: text/html\r\n"; - out += "\r\n"; - out += "<html><body>Greets. You said: "; - out += std::string(reinterpret_cast<const char*>(&m_client_data[0]), - m_client_data.size()); - out += "</body></html>\r\n\r\n"; - - m_tls.send(out); - m_tls.close(); - } - - bool tls_handshake_complete(const Botan::TLS::Session& session) - { - m_hostname = session.server_info().hostname(); - return true; - } - - boost::asio::io_service::strand m_strand; // serialization - - tcp::socket m_socket; - Botan::TLS::Server m_tls; - std::string m_hostname; - - unsigned char m_read_buf[1024]; - - // used to hold the data currently being written by the system - std::vector<byte> m_write_buf; - - // used to hold data queued for writing - std::vector<byte> m_outbox; - - std::vector<byte> m_client_data; - }; - -class asio_tls_server - { - public: - typedef tls_server_session session; - - asio_tls_server(boost::asio::io_service& io_service, unsigned short port) : - m_acceptor(io_service, tcp::endpoint(tcp::v4(), port)), - m_session_manager(m_rng), - m_creds(m_rng) - { - session::pointer new_session = make_session(); - - m_acceptor.async_accept( - new_session->socket(), - boost::bind( - &asio_tls_server::handle_accept, - this, - new_session, - boost::asio::placeholders::error) - ); - } - - private: - session::pointer make_session() - { - return session::create( - m_acceptor.get_io_service(), - m_session_manager, - m_creds, - m_policy, - m_rng - ); - } - - void handle_accept(session::pointer new_session, - const boost::system::error_code& error) - { - if (!error) - { - new_session->start(); - - new_session = make_session(); - - m_acceptor.async_accept( - new_session->socket(), - boost::bind( - &asio_tls_server::handle_accept, - this, - new_session, - boost::asio::placeholders::error) - ); - } - } - - tcp::acceptor m_acceptor; - - Botan::AutoSeeded_RNG m_rng; - Botan::TLS::Session_Manager_In_Memory m_session_manager; - Botan::TLS::Policy m_policy; - Credentials_Manager_Simple m_creds; - }; - -size_t choose_thread_count() - { - size_t result = std::thread::hardware_concurrency(); - - if(result) - return result; - - return 2; - } - -int tls_server_asio(int argc, char* argv[]) - { - try - { - boost::asio::io_service io_service; - - const unsigned short port = 4434; - asio_tls_server server(io_service, port); - - size_t num_threads = choose_thread_count(); - if(argc == 2) - std::istringstream(argv[1]) >> num_threads; - - std::cout << "Using " << num_threads << " threads\n"; - - std::vector<boost::shared_ptr<std::thread> > threads; - - for(size_t i = 0; i != num_threads; ++i) - { - boost::shared_ptr<std::thread> thread( - new std::thread( - boost::bind(&boost::asio::io_service::run, &io_service))); - threads.push_back(thread); - } - - // Wait for all threads in the pool to exit. - for (size_t i = 0; i < threads.size(); ++i) - threads[i]->join(); - } - catch (std::exception& e) - { - std::cerr << e.what() << std::endl; - } - - return 0; - } - -} - -REGISTER_APP(tls_server_asio); - -#endif |