From cbe0a306b366c2adc091167fe1b8f627a1ab4007 Mon Sep 17 00:00:00 2001 From: badaix Date: Tue, 7 May 2024 22:50:27 +0200 Subject: [PATCH] Use HTTPS, support for HTTP missing --- server/CMakeLists.txt | 1 - server/control_server.cpp | 23 ++++++----- server/control_server.hpp | 3 +- server/control_session_http.cpp | 56 ++++++++++++++++++++------ server/control_session_http.hpp | 18 +++++++-- server/control_session_ws.cpp | 13 +++--- server/control_session_ws.hpp | 7 +++- server/etc/snapserver.conf | 12 ++++++ server/server.cpp | 2 +- server/server_settings.hpp | 7 ++++ server/snapserver.cpp | 4 ++ server/stream_session_ws.cpp | 17 ++++---- server/stream_session_ws.hpp | 10 +++-- server/streamreader/stream_control.hpp | 2 +- 14 files changed, 121 insertions(+), 54 deletions(-) diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index f1011cbf..ae9ea910 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -100,7 +100,6 @@ endif() # list(APPEND SERVER_LIBRARIES Boost::boost) list(APPEND SERVER_LIBRARIES OpenSSL::Crypto OpenSSL::SSL) - include_directories(${SERVER_INCLUDE}) if(ANDROID) add_executable(libsnapserver.so ${SERVER_SOURCES}) diff --git a/server/control_server.cpp b/server/control_server.cpp index 70341e78..c8669be5 100644 --- a/server/control_server.cpp +++ b/server/control_server.cpp @@ -24,6 +24,7 @@ #include "common/json.hpp" #include "control_session_http.hpp" #include "control_session_tcp.hpp" +#include "server_settings.hpp" // 3rd party headers @@ -37,16 +38,16 @@ using json = nlohmann::json; static constexpr auto LOG_TAG = "ControlServer"; -ControlServer::ControlServer(boost::asio::io_context& io_context, const ServerSettings::Tcp& tcp_settings, const ServerSettings::Http& http_settings, - ControlMessageReceiver* controlMessageReceiver) - : io_context_(io_context), ssl_context_(boost::asio::ssl::context::sslv23), tcp_settings_(tcp_settings), http_settings_(http_settings), +ControlServer::ControlServer(boost::asio::io_context& io_context, const ServerSettings& settings, ControlMessageReceiver* controlMessageReceiver) + : io_context_(io_context), ssl_context_(boost::asio::ssl::context::sslv23), tcp_settings_(settings.tcp), http_settings_(settings.http), controlMessageReceiver_(controlMessageReceiver) { + const ServerSettings::Ssl& ssl = settings.ssl; ssl_context_.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use); ssl_context_.set_password_callback(std::bind(&ControlServer::getPassword, this)); - ssl_context_.use_certificate_chain_file("server.pem"); - ssl_context_.use_private_key_file("server.pem", boost::asio::ssl::context::pem); - ssl_context_.use_tmp_dh_file("dh4096.pem"); + ssl_context_.use_certificate_chain_file(ssl.certificate); + ssl_context_.use_private_key_file(ssl.private_key, boost::asio::ssl::context::pem); + // ssl_context_.use_tmp_dh_file("dh4096.pem"); } @@ -58,6 +59,7 @@ ControlServer::~ControlServer() std::string ControlServer::getPassword() const { + LOG(DEBUG, LOG_TAG) << "getPassword\n"; return "test"; } @@ -127,11 +129,10 @@ void ControlServer::startAccept() { if (!ec) { - handleAccept(std::move(socket), http_settings_); - // auto session = make_shared>>( - // this, boost::asio::ssl::stream(std::move(socket), ssl_context_), http_settings_); - // onNewSession(std::move(session)); - // startAccept(); + // handleAccept(std::move(socket), http_settings_); + auto session = make_shared(this, std::move(socket), ssl_context_, http_settings_); + onNewSession(std::move(session)); + startAccept(); } else LOG(ERROR, LOG_TAG) << "Error while accepting socket connection: " << ec.message() << "\n"; diff --git a/server/control_server.hpp b/server/control_server.hpp index d4251d5e..8f5d3f92 100644 --- a/server/control_server.hpp +++ b/server/control_server.hpp @@ -43,8 +43,7 @@ using acceptor_ptr = std::unique_ptr; class ControlServer : public ControlMessageReceiver { public: - ControlServer(boost::asio::io_context& io_context, const ServerSettings::Tcp& tcp_settings, const ServerSettings::Http& http_settings, - ControlMessageReceiver* controlMessageReceiver = nullptr); + ControlServer(boost::asio::io_context& io_context, const ServerSettings& settings, ControlMessageReceiver* controlMessageReceiver = nullptr); virtual ~ControlServer(); void start(); diff --git a/server/control_session_http.cpp b/server/control_session_http.cpp index 57b5a3b2..4656fc8b 100644 --- a/server/control_session_http.cpp +++ b/server/control_session_http.cpp @@ -21,14 +21,15 @@ // standard headers #include +#include // 3rd party headers +#include #include #include // local headers #include "common/aixlog.hpp" -#include "common/message/pcm_chunk.hpp" #include "common/utils/file_utils.hpp" #include "control_session_ws.hpp" #include "stream_session_ws.hpp" @@ -146,10 +147,11 @@ std::string path_cat(boost::beast::string_view base, boost::beast::string_view p } } // namespace -ControlSessionHttp::ControlSessionHttp(ControlMessageReceiver* receiver, tcp::socket&& socket, const ServerSettings::Http& settings) - : ControlSession(receiver), socket_(std::move(socket)), settings_(settings) +ControlSessionHttp::ControlSessionHttp(ControlMessageReceiver* receiver, tcp_socket&& socket, boost::asio::ssl::context& ssl_context, + const ServerSettings::Http& settings) + : ControlSession(receiver), socket_(ssl_socket(std::move(socket), ssl_context)), ssl_context_(ssl_context), settings_(settings) { - LOG(DEBUG, LOG_TAG) << "ControlSessionHttp, Local IP: " << socket_.local_endpoint().address().to_string() << "\n"; + LOG(DEBUG, LOG_TAG) << "ControlSessionHttp, Local IP: " << socket_.next_layer().local_endpoint().address().to_string() << "\n"; } @@ -160,9 +162,31 @@ ControlSessionHttp::~ControlSessionHttp() } +void ControlSessionHttp::doHandshake() +{ + LOG(DEBUG, LOG_TAG) << "doHandshake\n"; + socket_.async_handshake(boost::asio::ssl::stream_base::server, + [this, self = shared_from_this()](const boost::system::error_code& error) + { + LOG(DEBUG, LOG_TAG) << "async_handshake\n"; + if (error) + { + LOG(ERROR, LOG_TAG) << "async_handshake error: " << error.message() << "\n"; + } + else + { + http::async_read(socket_, buffer_, req_, + [this, self = shared_from_this()](boost::system::error_code ec, std::size_t bytes) { on_read(ec, bytes); }); + } + }); +} + + void ControlSessionHttp::start() { - http::async_read(socket_, buffer_, req_, [this, self = shared_from_this()](boost::system::error_code ec, std::size_t bytes) { on_read(ec, bytes); }); + LOG(DEBUG, LOG_TAG) << "start\n"; + doHandshake(); + // http::async_read(socket_, buffer_, req_, [this, self = shared_from_this()](boost::system::error_code ec, std::size_t bytes) { on_read(ec, bytes); }); } @@ -331,7 +355,11 @@ void ControlSessionHttp::on_read(beast::error_code ec, std::size_t bytes_transfe // This means they closed the connection if (ec == http::error::end_of_stream) { - socket_.shutdown(tcp::socket::shutdown_send, ec); + boost::system::error_code res; + res = socket_.shutdown(res); + // auto res = socket_.lowest_layer().shutdown(tcp_socket::shutdown_send, ec); + if (res.failed()) + LOG(ERROR, LOG_TAG) << "Failed to shudown socket: " << res << "\n"; return; } @@ -352,14 +380,14 @@ void ControlSessionHttp::on_read(beast::error_code ec, std::size_t bytes_transfe if (req_.target() == "/jsonrpc") { // Create a WebSocket session by transferring the socket - // std::make_shared(std::move(socket_), state_)->run(std::move(req_)); - auto ws = std::make_shared>(std::move(socket_)); + auto ws = std::make_shared>(std::move(socket_)); + // Accept the websocket handshake ws->async_accept(req_, - [this, ws, self = shared_from_this()](beast::error_code ec) + [this, ws, self = shared_from_this()](beast::error_code ec) mutable { if (ec) { - LOG(ERROR, LOG_TAG) << "Error during WebSocket handshake (control): " << ec.message() << "\n"; + LOG(ERROR, LOG_TAG) << "Error during WebSocket accept (control): " << ec.message() << "\n"; } else { @@ -372,7 +400,7 @@ void ControlSessionHttp::on_read(beast::error_code ec, std::size_t bytes_transfe { // Create a WebSocket session by transferring the socket // std::make_shared(std::move(socket_), state_)->run(std::move(req_)); - auto ws = std::make_shared>(std::move(socket_)); + auto ws = std::make_shared>(std::move(socket_)); ws->async_accept(req_, [this, ws, self = shared_from_this()](beast::error_code ec) { @@ -422,7 +450,11 @@ void ControlSessionHttp::on_write(beast::error_code ec, std::size_t bytes, bool { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. - socket_.shutdown(tcp::socket::shutdown_send, ec); + boost::system::error_code res; + res = socket_.shutdown(res); + // auto res = socket_.lowest_layer().shutdown(tcp::socket::shutdown_send, ec); + if (res.failed()) + LOG(ERROR, LOG_TAG) << "Failed to shudown socket: " << res << "\n"; return; } diff --git a/server/control_session_http.hpp b/server/control_session_http.hpp index 885232ab..dc01f460 100644 --- a/server/control_session_http.hpp +++ b/server/control_session_http.hpp @@ -24,7 +24,10 @@ #include "server_settings.hpp" // 3rd party headers +#include +#include #include +#include #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push @@ -37,12 +40,13 @@ // standard headers #include - -using boost::asio::ip::tcp; +// #include namespace beast = boost::beast; // from namespace http = beast::http; // from +using tcp_socket = boost::asio::ip::tcp::socket; +using ssl_socket = boost::asio::ssl::stream; /// Endpoint for a connected control client. /** @@ -54,7 +58,7 @@ class ControlSessionHttp : public ControlSession { public: /// ctor. Received message from the client are passed to ControlMessageReceiver - ControlSessionHttp(ControlMessageReceiver* receiver, tcp::socket&& socket, const ServerSettings::Http& settings); + ControlSessionHttp(ControlMessageReceiver* receiver, tcp_socket&& socket, boost::asio::ssl::context& ssl_context, const ServerSettings::Http& settings); ~ControlSessionHttp() override; void start() override; void stop() override; @@ -72,8 +76,14 @@ class ControlSessionHttp : public ControlSession http::request req_; + // do SSL handshake + void doHandshake(); + protected: - tcp::socket socket_; + // tcp_socket socket_; + ssl_socket socket_; + // std::variant sock_; + boost::asio::ssl::context& ssl_context_; beast::flat_buffer buffer_; ServerSettings::Http settings_; std::deque messages_; diff --git a/server/control_session_ws.cpp b/server/control_session_ws.cpp index 46520ea3..227b3b1d 100644 --- a/server/control_session_ws.cpp +++ b/server/control_session_ws.cpp @@ -21,7 +21,6 @@ // local headers #include "common/aixlog.hpp" -#include "common/message/pcm_chunk.hpp" // 3rd party headers @@ -33,8 +32,8 @@ using namespace std; static constexpr auto LOG_TAG = "ControlSessionWS"; -ControlSessionWebsocket::ControlSessionWebsocket(ControlMessageReceiver* receiver, websocket::stream&& socket) - : ControlSession(receiver), ws_(std::move(socket)), strand_(boost::asio::make_strand(ws_.get_executor())) +ControlSessionWebsocket::ControlSessionWebsocket(ControlMessageReceiver* receiver, websocket::stream&& wss) + : ControlSession(receiver), wss_(std::move(wss)), strand_(boost::asio::make_strand(wss_.get_executor())) { LOG(DEBUG, LOG_TAG) << "ControlSessionWebsocket\n"; } @@ -85,9 +84,9 @@ void ControlSessionWebsocket::sendAsync(const std::string& message) void ControlSessionWebsocket::send_next() { const std::string& message = messages_.front(); - ws_.async_write(boost::asio::buffer(message), - [this, self = shared_from_this()](std::error_code ec, std::size_t length) - { + wss_.async_write(boost::asio::buffer(message), + [this, self = shared_from_this()](std::error_code ec, std::size_t length) + { messages_.pop_front(); if (ec) { @@ -106,7 +105,7 @@ void ControlSessionWebsocket::send_next() void ControlSessionWebsocket::do_read_ws() { // Read a message into our buffer - ws_.async_read(buffer_, [this, self = shared_from_this()](beast::error_code ec, std::size_t bytes_transferred) { on_read_ws(ec, bytes_transferred); }); + wss_.async_read(buffer_, [this, self = shared_from_this()](beast::error_code ec, std::size_t bytes_transferred) { on_read_ws(ec, bytes_transferred); }); } diff --git a/server/control_session_ws.hpp b/server/control_session_ws.hpp index fa3c4c52..18c2e9b7 100644 --- a/server/control_session_ws.hpp +++ b/server/control_session_ws.hpp @@ -32,6 +32,7 @@ #endif #include #pragma GCC diagnostic pop +#include #include // standard headers @@ -40,6 +41,8 @@ namespace beast = boost::beast; // from namespace websocket = beast::websocket; // from +using tcp_socket = boost::asio::ip::tcp::socket; +using ssl_socket = boost::asio::ssl::stream; /// Endpoint for a connected control client. @@ -52,7 +55,7 @@ class ControlSessionWebsocket : public ControlSession { public: /// ctor. Received message from the client are passed to ControlMessageReceiver - ControlSessionWebsocket(ControlMessageReceiver* receiver, websocket::stream&& socket); + ControlSessionWebsocket(ControlMessageReceiver* receiver, websocket::stream&& wss); ~ControlSessionWebsocket() override; void start() override; void stop() override; @@ -66,7 +69,7 @@ class ControlSessionWebsocket : public ControlSession void do_read_ws(); void send_next(); - websocket::stream ws_; + websocket::stream wss_; protected: beast::flat_buffer buffer_; diff --git a/server/etc/snapserver.conf b/server/etc/snapserver.conf index 5f2e30c2..91070906 100644 --- a/server/etc/snapserver.conf +++ b/server/etc/snapserver.conf @@ -47,6 +47,18 @@ ############################################################################### +# Secure Socket Layer ######################################################### +# +[ssl] +# https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/ +# https://gist.github.com/fntlnz/cf14feb5a46b2eda428e000157447309 +certificate = certs/snapserver.crt +private_key = certs/snapserver.key + +# +############################################################################### + + # HTTP RPC #################################################################### # [http] diff --git a/server/server.cpp b/server/server.cpp index 6a450fdb..cdfd8533 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -871,7 +871,7 @@ void Server::start() { try { - controlServer_ = std::make_unique(io_context_, settings_.tcp, settings_.http, this); + controlServer_ = std::make_unique(io_context_, settings_, this); streamServer_ = std::make_unique(io_context_, settings_, this); streamManager_ = std::make_unique(this, io_context_, settings_); diff --git a/server/server_settings.hpp b/server/server_settings.hpp index 9cb34ffd..ac427628 100644 --- a/server/server_settings.hpp +++ b/server/server_settings.hpp @@ -38,6 +38,12 @@ struct ServerSettings std::string data_dir{""}; }; + struct Ssl + { + std::string certificate{""}; + std::string private_key{""}; + }; + struct Http { bool enabled{true}; @@ -79,6 +85,7 @@ struct ServerSettings }; Server server; + Ssl ssl; Http http; Tcp tcp; Stream stream; diff --git a/server/snapserver.cpp b/server/snapserver.cpp index 875584e0..d1f99f84 100644 --- a/server/snapserver.cpp +++ b/server/snapserver.cpp @@ -80,6 +80,10 @@ int main(int argc, char* argv[]) conf.add>("", "server.group", "the group to run as when daemonized", settings.server.group, &settings.server.group); conf.add>("", "server.datadir", "directory where persistent data is stored", settings.server.data_dir, &settings.server.data_dir); + // SSL settings + conf.add>("", "ssl.certificate", "certificate file (PEM format)", settings.ssl.certificate, &settings.ssl.certificate); + conf.add>("", "ssl.private_key", "private key file (PEM format)", settings.ssl.private_key, &settings.ssl.private_key); + // HTTP RPC settings conf.add>("", "http.enabled", "enable HTTP Json RPC (HTTP POST and websockets)", settings.http.enabled, &settings.http.enabled); conf.add>("", "http.port", "which port the server should listen on", settings.http.port, &settings.http.port); diff --git a/server/stream_session_ws.cpp b/server/stream_session_ws.cpp index 5f666618..3a42ec52 100644 --- a/server/stream_session_ws.cpp +++ b/server/stream_session_ws.cpp @@ -21,7 +21,6 @@ // local headers #include "common/aixlog.hpp" -#include "common/message/pcm_chunk.hpp" // 3rd party headers @@ -33,8 +32,8 @@ using namespace std; static constexpr auto LOG_TAG = "StreamSessionWS"; -StreamSessionWebsocket::StreamSessionWebsocket(StreamMessageReceiver* receiver, websocket::stream&& socket) - : StreamSession(socket.get_executor(), receiver), ws_(std::move(socket)) +StreamSessionWebsocket::StreamSessionWebsocket(StreamMessageReceiver* receiver, websocket::stream&& wss) + : StreamSession(wss.get_executor(), receiver), wss_(std::move(wss)) { LOG(DEBUG, LOG_TAG) << "StreamSessionWS\n"; } @@ -51,17 +50,17 @@ void StreamSessionWebsocket::start() { // Read a message LOG(DEBUG, LOG_TAG) << "start\n"; - ws_.binary(true); + wss_.binary(true); do_read_ws(); } void StreamSessionWebsocket::stop() { - if (ws_.is_open()) + if (wss_.is_open()) { boost::beast::error_code ec; - ws_.close(beast::websocket::close_code::normal, ec); + wss_.close(beast::websocket::close_code::normal, ec); if (ec) LOG(ERROR, LOG_TAG) << "Error in socket close: " << ec.message() << "\n"; } @@ -72,7 +71,7 @@ std::string StreamSessionWebsocket::getIP() { try { - return ws_.next_layer().socket().remote_endpoint().address().to_string(); + return wss_.next_layer().lowest_layer().remote_endpoint().address().to_string(); } catch (...) { @@ -84,14 +83,14 @@ std::string StreamSessionWebsocket::getIP() void StreamSessionWebsocket::sendAsync(const shared_const_buffer& buffer, const WriteHandler& handler) { LOG(TRACE, LOG_TAG) << "sendAsync: " << buffer.message().type << "\n"; - ws_.async_write(buffer, [self = shared_from_this(), buffer, handler](boost::system::error_code ec, std::size_t length) { handler(ec, length); }); + wss_.async_write(buffer, [self = shared_from_this(), buffer, handler](boost::system::error_code ec, std::size_t length) { handler(ec, length); }); } void StreamSessionWebsocket::do_read_ws() { // Read a message into our buffer - ws_.async_read(buffer_, [this, self = shared_from_this()](beast::error_code ec, std::size_t bytes_transferred) { on_read_ws(ec, bytes_transferred); }); + wss_.async_read(buffer_, [this, self = shared_from_this()](beast::error_code ec, std::size_t bytes_transferred) { on_read_ws(ec, bytes_transferred); }); } diff --git a/server/stream_session_ws.hpp b/server/stream_session_ws.hpp index bac85244..dc4a0ec4 100644 --- a/server/stream_session_ws.hpp +++ b/server/stream_session_ws.hpp @@ -32,14 +32,16 @@ #endif #include #pragma GCC diagnostic pop +#include #include // standard headers -namespace beast = boost::beast; // from -// namespace http = beast::http; // from +namespace beast = boost::beast; // from namespace websocket = beast::websocket; // from +using tcp_socket = boost::asio::ip::tcp::socket; +using ssl_socket = boost::asio::ssl::stream; /// Endpoint for a connected control client. @@ -52,7 +54,7 @@ class StreamSessionWebsocket : public StreamSession { public: /// ctor. Received message from the client are passed to StreamMessageReceiver - StreamSessionWebsocket(StreamMessageReceiver* receiver, websocket::stream&& socket); + StreamSessionWebsocket(StreamMessageReceiver* receiver, websocket::stream&& wss); ~StreamSessionWebsocket() override; void start() override; void stop() override; @@ -64,7 +66,7 @@ class StreamSessionWebsocket : public StreamSession void on_read_ws(beast::error_code ec, std::size_t bytes_transferred); void do_read_ws(); - websocket::stream ws_; + websocket::stream wss_; protected: beast::flat_buffer buffer_; diff --git a/server/streamreader/stream_control.hpp b/server/streamreader/stream_control.hpp index ec719e19..cad102f1 100644 --- a/server/streamreader/stream_control.hpp +++ b/server/streamreader/stream_control.hpp @@ -61,7 +61,7 @@ class StreamControl using OnResponse = std::function; using OnLog = std::function; - StreamControl(const boost::asio::any_io_executor& executor); + explicit StreamControl(const boost::asio::any_io_executor& executor); virtual ~StreamControl() = default; void start(const std::string& stream_id, const ServerSettings& server_setttings, const OnNotification& notification_handler,