#include "https.h" #include "config.h" #include "file.h" #include "server.h" #include "response.h" #include #include #include #include #include #include #include #ifdef BOOST_LATEST #include #else #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include namespace beast = boost::beast; // from namespace http = beast::http; // from namespace net = boost::asio; // from namespace ssl = boost::asio::ssl; // from using tcp = boost::asio::ip::tcp; // from namespace { //------------------------------------------------------------------------------ // Report a failure void fail( #ifdef BOOST_LATEST beast::error_code ec, #else boost::system::error_code ec, #endif char const* what) { #ifdef BOOST_LATEST // ssl::error::stream_truncated, also known as an SSL "short read", // indicates the peer closed the connection without performing the // required closing handshake (for example, Google does this to // improve performance). Generally this can be a security issue, // but if your communication protocol is self-terminated (as // it is with both HTTP and WebSocket) then you may simply // ignore the lack of close_notify. // // https://github.com/boostorg/beast/issues/38 // // https://security.stackexchange.com/questions/91435/how-to-handle-a-malicious-ssl-tls-shutdown // // When a short read would cut off the end of an HTTP message, // Beast returns the error beast::http::error::partial_message. // Therefore, if we see a short read here, it has occurred // after the message has been completed, so it is safe to ignore it. if(ec == net::ssl::error::stream_truncated) return; #endif std::cerr << what << ": " << ec.message() << "\n"; } // Handles an HTTP server connection class session : public std::enable_shared_from_this { #ifdef BOOST_LATEST beast::ssl_stream stream_; #else tcp::socket socket_; ssl::stream stream_; boost::asio::strand strand_; #endif beast::flat_buffer buffer_; Server& m_server; std::optional> parser_; // need to reset parser every time, no other mechanism currently http::request req_; std::shared_ptr res_; void handle_request(::Server& server, request_type&& req) { auto sp = std::make_shared(generate_response(req, server)); res_ = sp; // Write the response #ifdef BOOST_LATEST http::async_write( stream_, *sp, beast::bind_front_handler( &session::on_write, shared_from_this(), sp->need_eof())); #else http::async_write( stream_, *sp, boost::asio::bind_executor( strand_, std::bind( &session::on_write, shared_from_this(), std::placeholders::_1, std::placeholders::_2, sp->need_eof()))); #endif } public: // Take ownership of the socket explicit session( #ifdef BOOST_LATEST tcp::socket&& socket, #else tcp::socket socket, #endif ssl::context& ctx, Server& server) #ifdef BOOST_LATEST : stream_(std::move(socket), ctx) #else : socket_(std::move(socket)) , stream_(socket_, ctx) , strand_(socket_.get_executor()) #endif , m_server(server) { } // Start the asynchronous operation void run() { #ifdef BOOST_LATEST // We need to be executing within a strand to perform async operations // on the I/O objects in this session. net::dispatch( stream_.get_executor(), beast::bind_front_handler( &session::on_run, shared_from_this())); #else stream_.async_handshake( ssl::stream_base::server, boost::asio::bind_executor( strand_, std::bind( &session::on_handshake, shared_from_this(), std::placeholders::_1))); #endif } #ifdef BOOST_LATEST void on_run() { // Set the timeout. beast::get_lowest_layer(stream_).expires_after( std::chrono::seconds(30)); // Perform the SSL handshake stream_.async_handshake( ssl::stream_base::server, beast::bind_front_handler( &session::on_handshake, shared_from_this())); } #endif void on_handshake( #ifdef BOOST_LATEST beast::error_code ec #else boost::system::error_code ec #endif ) { if(ec) return fail(ec, "handshake"); do_read(); } void do_read() { // Make the request empty before reading, // otherwise the operation behavior is undefined. req_ = {}; // this is the way to reset the parser. it's necessary. // https://github.com/boostorg/beast/issues/927 parser_.emplace(); parser_->body_limit(1000000000); // 1GB limit #ifdef BOOST_LATEST // Set the timeout. beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); // Read a request http::async_read(stream_, buffer_, *parser_, beast::bind_front_handler( &session::on_read, shared_from_this())); #else http::async_read(stream_, buffer_, *parser_, boost::asio::bind_executor( strand_, std::bind( &session::on_read, shared_from_this(), std::placeholders::_1, std::placeholders::_2))); #endif } void on_read( #ifdef BOOST_LATEST beast::error_code ec, #else boost::system::error_code ec, #endif std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); // This means they closed the connection if(ec == http::error::end_of_stream) return do_close(); if(ec) return fail(ec, "read"); req_ = parser_->get(); // Send the response handle_request(m_server, std::move(req_)); } void on_write( #ifdef BOOST_LATEST bool close, beast::error_code ec, std::size_t bytes_transferred #else boost::system::error_code ec, std::size_t bytes_transferred, bool close #endif ) { boost::ignore_unused(bytes_transferred); if(ec) return fail(ec, "write"); if(close) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. return do_close(); } // We're done with the response so delete it res_ = nullptr; // Read another request do_read(); } void do_close() { #ifdef BOOST_LATEST // Set the timeout. beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); // Perform the SSL shutdown stream_.async_shutdown( beast::bind_front_handler( &session::on_shutdown, shared_from_this())); #else stream_.async_shutdown( boost::asio::bind_executor( strand_, std::bind( &session::on_shutdown, shared_from_this(), std::placeholders::_1))); #endif } void on_shutdown(beast::error_code ec) { if(ec) return fail(ec, "shutdown"); // At this point the connection is closed gracefully } }; //------------------------------------------------------------------------------ // Accepts incoming connections and launches the sessions class listener : public std::enable_shared_from_this { #ifdef BOOST_LATEST net::io_context& ioc_; #endif ssl::context& ctx_; tcp::acceptor acceptor_; #ifndef BOOST_LATEST tcp::socket socket_; #endif ::Server& m_server; public: listener( net::io_context& ioc, ssl::context& ctx, tcp::endpoint endpoint, Server& server) : #ifdef BOOST_LATEST ioc_(ioc), #endif ctx_(ctx) , acceptor_(ioc) #ifndef BOOST_LATEST , socket_(ioc) #endif , m_server(server) { #ifdef BOOST_LATEST beast::error_code ec; #else boost::system::error_code ec; #endif // Open the acceptor acceptor_.open(endpoint.protocol(), ec); if(ec) { fail(ec, "open"); return; } // Allow address reuse acceptor_.set_option(net::socket_base::reuse_address(true), ec); if(ec) { fail(ec, "set_option"); return; } // Bind to the server address acceptor_.bind(endpoint, ec); if(ec) { fail(ec, "bind"); return; } // Start listening for connections acceptor_.listen( net::socket_base::max_listen_connections, ec); if(ec) { fail(ec, "listen"); return; } } // Start accepting incoming connections void run() { #ifndef BOOST_LATEST if(! acceptor_.is_open()) return; #endif do_accept(); } private: void do_accept() { // The new connection gets its own strand #ifdef BOOST_LATEST acceptor_.async_accept( net::make_strand(ioc_), beast::bind_front_handler( &listener::on_accept, shared_from_this())); #else acceptor_.async_accept( socket_, std::bind( &listener::on_accept, shared_from_this(), std::placeholders::_1)); #endif } void #ifdef BOOST_LATEST on_accept(beast::error_code ec, tcp::socket socket) #else on_accept(boost::system::error_code ec) #endif { if(ec) { fail(ec, "accept"); } else { // Create the session and run it std::make_shared( #ifdef BOOST_LATEST std::move(socket), #else std::move(socket_), #endif ctx_, m_server)->run(); } // Accept another connection do_accept(); } }; /* Load a signed certificate into the ssl context, and configure the context for use with a server. For this to work with the browser or operating system, it is necessary to import the "Beast Test CA" certificate into the local certificate store, browser, or operating system depending on your environment Please see the documentation accompanying the Beast certificate for more details. */ void load_server_certificate(boost::asio::ssl::context& ctx, fs::path cert_path, fs::path key_path) { /* The certificate was generated from CMD.EXE on Windows 10 using: winpty openssl dhparam -out dh.pem 2048 winpty openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 10000 -out cert.pem -subj "//C=US\ST=CA\L=Los Angeles\O=Beast\CN=www.example.com" */ std::string const dh = "-----BEGIN DH PARAMETERS-----\n" "MIIBCAKCAQEArzQc5mpm0Fs8yahDeySj31JZlwEphUdZ9StM2D8+Fo7TMduGtSi+\n" "/HRWVwHcTFAgrxVdm+dl474mOUqqaz4MpzIb6+6OVfWHbQJmXPepZKyu4LgUPvY/\n" "4q3/iDMjIS0fLOu/bLuObwU5ccZmDgfhmz1GanRlTQOiYRty3FiOATWZBRh6uv4u\n" "tff4A9Bm3V9tLx9S6djq31w31Gl7OQhryodW28kc16t9TvO1BzcV3HjRPwpe701X\n" "oEEZdnZWANkkpR/m/pfgdmGPU66S2sXMHgsliViQWpDCYeehrvFRHEdR9NV+XJfC\n" "QMUk26jPTIVTLfXmmwU0u8vUkpR7LQKkwwIBAg==\n" "-----END DH PARAMETERS-----\n"; ctx.set_password_callback( [](std::size_t, boost::asio::ssl::context_base::password_purpose) { return "test"; }); ctx.set_options( boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use); std::string cert; if (cert_path == "") { // generate dummy self signed certificate. Will be replaced by real // certificate if configured upon respective session cert = "-----BEGIN CERTIFICATE-----\n" "MIIDnTCCAoWgAwIBAgIULkYtO+2Ddeg+qLZ+aDQpmA5b4L0wDQYJKoZIhvcNAQEL\n" "BQAwXjELMAkGA1UEBhMCREUxEDAOBgNVBAgMB0JhdmFyaWExDzANBgNVBAcMBk11\n" "bmljaDEVMBMGA1UECgwMUmVpY2h3ZWluIElUMRUwEwYDVQQDDAxyZWljaHdlaW4u\n" "aXQwHhcNMjAwNDA1MDgwNzIyWhcNNDcwODIyMDgwNzIyWjBeMQswCQYDVQQGEwJE\n" "RTEQMA4GA1UECAwHQmF2YXJpYTEPMA0GA1UEBwwGTXVuaWNoMRUwEwYDVQQKDAxS\n" "ZWljaHdlaW4gSVQxFTATBgNVBAMMDHJlaWNod2Vpbi5pdDCCASIwDQYJKoZIhvcN\n" "AQEBBQADggEPADCCAQoCggEBALJNb0WLbz+xP+YITMMk+eeK/SIOCRFs/9aZIAyK\n" "ParGauxa+8d25mlfJTAo6/G0h3sA240JHyNpOzVOogPU+v4dRWyGO0w5vHVD0caB\n" "rDb1eEfmLtqfKLLUL9iPDReUh6WAE7qoNDtfoT551uSMIae1cpPUduVTnSkEgw8k\n" "NjJSHYT800jSB2R+e7tJG3ErXDM63R3B8RbitZPoWACjpBxDT+Qrj0fBFS4AWw6b\n" "z09uitv0RrgI6CW7xRh3UAdRwEBGHiU6HTIthX6LNgez1UL0sfu1iZ22wNmYZP/S\n" "sL3b20WtSH9LN2PRJ4q3AGt6RMbmSGr65ljha9xkTFna0Y8CAwEAAaNTMFEwHQYD\n" "VR0OBBYEFKd5/MGFZUAUV502vJ/Kcswax8WVMB8GA1UdIwQYMBaAFKd5/MGFZUAU\n" "V502vJ/Kcswax8WVMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB\n" "AIBS4AfM7wiunQ2UZQQ5A0Un99+BLax9e+h11h/jGeJ+/9maY/E9MK6UG9LXoOv2\n" "z32Q7Ta2xKeRu6GC/qupwYJ0Xt3LENOfogsaNCAgxKlAN48LGlRyCTvzWsEMh28j\n" "RaelWonh2qQoiryKLVnRwrg8g1Bu4v+V437cIBmeZPxf0spEL9EVqlN+iS8plmel\n" "7/F4ULdybKGq39tgicuS7JhnY21ZzOFoq0bWnKBbAeTndmuROdb3pEppxW6pwu0q\n" "TFdMrSJE38kiQh2O9IchPQbTZ+Rdj0HE9NxStlrNr5bu6rjikRm50/G3JoXpzYdp\n" "AN4ZI2QZ6R6Y+TzDixKecNk=\n" "-----END CERTIFICATE-----\n" ; } else { cert = File::getFile(cert_path); } ctx.use_certificate_chain( boost::asio::buffer(cert.data(), cert.size())); std::string key; if (key_path == "") { // generate dummy self signed certificate. Will be replaced by real // certificate if configured upon respective session key = "-----BEGIN PRIVATE KEY-----\n" "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyTW9Fi28/sT/m\n" "CEzDJPnniv0iDgkRbP/WmSAMij2qxmrsWvvHduZpXyUwKOvxtId7ANuNCR8jaTs1\n" "TqID1Pr+HUVshjtMObx1Q9HGgaw29XhH5i7anyiy1C/Yjw0XlIelgBO6qDQ7X6E+\n" "edbkjCGntXKT1HblU50pBIMPJDYyUh2E/NNI0gdkfnu7SRtxK1wzOt0dwfEW4rWT\n" "6FgAo6QcQ0/kK49HwRUuAFsOm89Pborb9Ea4COglu8UYd1AHUcBARh4lOh0yLYV+\n" "izYHs9VC9LH7tYmdtsDZmGT/0rC929tFrUh/Szdj0SeKtwBrekTG5khq+uZY4Wvc\n" "ZExZ2tGPAgMBAAECggEBAK9bJKIa3dCgPB257/TEOtsTgJyrfROcRYkCk9iBZOC9\n" "v46wdIrZTwY2wtY4iMPwLoY0c7ijTfJ/nfFxYjmujyK4Gvz+jvcKmWQizP8TrRFo\n" "HWFo6o+slFQ8BspO9itIspd7/OtIXgY+qNBO959Sig7sjsEA5eXoc9pRS6vqizq0\n" "j4G/UO5Amr/l3ciEJiqMJgZsIVLDKaGlqFTymydSqkB8UHQYWK1kunQxhK4Ldycu\n" "hTooQE7tXM0zvoFVV6v1fldV5OFsZk2kPMNtvMO6ZEpOM4rNMlg+vJy8kB1fb3Gs\n" "iFE/DCUpZsMSserQMU9/hfrYlndgsFD5Sr1EVGEebhECgYEA1gc9qx+ugdhYTY5j\n" "tJDXjOsnw8KY/l/1y+mQ8XNJ9MVdBGy1WB+uWB4teiyJchV49gn2XlKUK2rcCBvZ\n" "vC5CwPmFi2t70JezQgnXtDlbR0bARPlRd741i4rBpD7hEiZNCTOd2HFBpUg/CGWN\n" "E4n1ksazBm6jvv3Jo6WAa07Z390CgYEA1USrFqmc/fKGQpTCdH0qYZv3hQtrb1TQ\n" "9YnrbhtaC0haPpintZKjvhU3tCd1fPuIDXtMAgaaKSyoGiE2aMvLxt1/eV08BkMi\n" "kGIss9poYNi5+6ZD9QAHmHJhzZtVGj8U5L8379XmwxAByiBRVVE8CW1X/e6+iJpz\n" "+CLgN+zEVlsCgYEAsuOAdtxXJm4meERwL8b0cvNF3Eh1Sf/42MPTAwzCntSrh3w5\n" "InvwY/RtPHWnN/ScksEG7BWHhLafTCPDHJdp8hNcvIhNB68UBDln0loyYePP5pag\n" "sj4IUSbb7SUlR989elhrMTKQlM5K6QDAJrmjyVdM4S5urL9A3wgAyzAvyP0CgYAO\n" "paGuc8WxdzebWQYl4/bGL2UHgSpGwid7xZYiwMQlZDm2dNuHz+NpCaICwHcEN243\n" "ptEojnWGAGgnK0LGXcDIDqxTlICr2W6FRgjV7Vkf1aKoUtn1+KOM58YpzdJBdDWm\n" "JC/eS+2GVhIZZLDRUDv0VcsmSIBTd3AhiZumm588YwKBgBZfNqfmHAwIP2pM1wml\n" "Ck3vaLLvonghj3iQW9CFJ/SqLOnfT4KJkFObR6oGbxY0RtXsCrmSqidIKgDd0Kkq\n" "L6QbHp2j3+16GBdmLNUJlfjBTNPJp69IDKztjeCX7/8JZs79p/LAv+I9Sh4lVw4O\n" "IrDprlB0yzP5zigcsAZeViYJ\n" "-----END PRIVATE KEY-----\n" ; } else { key = File::getFile(key_path); } ctx.use_private_key( boost::asio::buffer(key.data(), key.size()), boost::asio::ssl::context::file_format::pem); ctx.use_tmp_dh( boost::asio::buffer(dh.data(), dh.size())); } int ServerNameError(SSL *s, HTTPS::Server::ctx_type& ctx_map) { std::shared_ptr ctx{ctx_map.at("")}; SSL_set_SSL_CTX(s, ctx->native_handle()); return SSL_CLIENT_HELLO_SUCCESS; // OK for now } int servername_callback(SSL *s, int *al, void *arg) { HTTPS::Server::ctx_type& ctx_map = *(HTTPS::Server::ctx_type*)arg; if (0) { // not really necessary int* numbers; size_t numbers_size; if (SSL_client_hello_get1_extensions_present(s, &numbers, &numbers_size) != 1) { std::cout << "Error on SSL_client_hello_get1_extensions_present" << std::endl; return ServerNameError(s, ctx_map); } bool server_name_available {false}; for (size_t i = 0; i < numbers_size; i++) if (numbers[i] == 0) server_name_available = true; OPENSSL_free(numbers); if (!server_name_available) { std::cout << "Error: No server_name available at SSL_client_hello_get1_extensions_present" << std::endl; return ServerNameError(s, ctx_map); } } const unsigned char* data; size_t data_size; // 0 is server_name if (SSL_client_hello_get0_ext(s, 0, &data, &data_size) != 1) { std::cout << "Warning: Error on SSL_client_hello_get0_ext: servername not available. Using dummy ctx." << std::endl; return ServerNameError(s, ctx_map); } // SNI Server Name, See https://tools.ietf.org/html/rfc6066 (TODO: why are there 5 bytes header?) std::string server_name {std::string((const char*)data, (size_t)data_size)}; if (server_name.size() >= 5 && server_name[0] == '\0') server_name = server_name.substr(5); auto it {ctx_map.find(server_name)}; std::shared_ptr ctx{}; if (it != ctx_map.end()) { ctx = it->second; } else { std::cout << "Warning: server_name " << server_name << " (" << server_name.size() << ") not found in list of prepared contexts. Using dummy ctx." << std::endl; return ServerNameError(s, ctx_map); } SSL_set_SSL_CTX(s, ctx->native_handle()); return SSL_CLIENT_HELLO_SUCCESS; } } // anonymous namespace //------------------------------------------------------------------------------ namespace HTTPS { Server::Server(Config& config, boost::asio::io_context& ioc, const Socket& socket, plugins_container_type& plugins, Statistics& statistics) : ::Server(config, ioc, socket, plugins, statistics) { // initial dummy, before we can add specific ctx w/ certificate std::shared_ptr ctx_dummy{std::make_shared(tls_method)}; load_server_certificate(*ctx_dummy, "", ""); SSL_CTX_set_client_hello_cb(ctx_dummy->native_handle(), servername_callback, &m_ctx); m_ctx.emplace("", ctx_dummy); // import the real certificates for (const auto& serve_site: socket.serve_sites) { for (const auto& site: config.Sites()) { if (site.name == serve_site) { std::shared_ptr ctx {std::make_shared(tls_method)}; std::cout << "Creating SSL context/cert for site " << serve_site << " on port " << socket.port << std::endl; load_server_certificate(*ctx, site.cert_path, site.key_path); SSL_CTX_set_client_hello_cb(ctx->native_handle(), servername_callback, &m_ctx); for (const auto& host: site.hosts) { std::cout << " Adding Host " << host << std::endl; m_ctx.emplace(host, ctx); } } } } } Server::~Server() { } int Server::start() { auto const address = net::ip::make_address(m_socket.address); auto const port = static_cast(std::atoi(m_socket.port.data())); // Create and launch a listening port std::make_shared( m_ioc, *m_ctx[""], tcp::endpoint{address, port}, *this)->run(); return EXIT_SUCCESS; } } // namespace HTTPS