From acb2a207d9f016701aae28667f96680b4492f4ad Mon Sep 17 00:00:00 2001 From: emeric Date: Wed, 25 Jun 2014 17:19:50 +0200 Subject: [PATCH] Remote protocol now over SSL --- configure.ac | 10 +++++ remote/server/Connection.cpp | 55 +++++++++++++++++++------ remote/server/Connection.hpp | 15 +++++-- remote/server/Server.cpp | 24 +++++++++-- remote/server/Server.hpp | 7 ++-- test/RemoteClientServer.cpp | 78 +++++++++++++++--------------------- 6 files changed, 121 insertions(+), 68 deletions(-) diff --git a/configure.ac b/configure.ac index 3c772b10..b12b3bbc 100644 --- a/configure.ac +++ b/configure.ac @@ -37,6 +37,16 @@ AC_CHECK_LIB([avformat], , [AC_MSG_ERROR([libavformat not found!])]) +AC_CHECK_LIB([ssl], + [SSL_CTX_new], + , + [AC_MSG_ERROR([libssl not found!])]) + +AC_CHECK_LIB([crypto], + [ASN1_STRING_to_UTF8], + , + [AC_MSG_ERROR([libcrypto not found!])]) + AC_CHECK_LIB([boost_system], [main], , diff --git a/remote/server/Connection.cpp b/remote/server/Connection.cpp index 0737f8fa..a98e1e19 100644 --- a/remote/server/Connection.cpp +++ b/remote/server/Connection.cpp @@ -15,26 +15,49 @@ namespace Remote { namespace Server { -Connection::Connection(boost::asio::ip::tcp::socket socket, +Connection::Connection(boost::asio::io_service& ioService, + boost::asio::ssl::context& context, ConnectionManager& manager, RequestHandler& handler) : _closing(false), -_socket(std::move(socket)), +_socket(ioService, context), _connectionManager(manager), _requestHandler(handler) { std::cout << "Server::Connection::Connection, Creating connection" << std::endl; } -boost::asio::ip::tcp::socket& -Connection::socket() -{ - return _socket; -} - void Connection::start() { + std::cout << "Starting connection..." << std::endl; + _socket.async_handshake(boost::asio::ssl::stream_base::server, + boost::bind(&Connection::handleHandshake, this, + boost::asio::placeholders::error)); +} + +void +Connection::handleHandshake(const boost::system::error_code& error) +{ + if (!error) + { + std::cout << "Handshake successfully performed... Now reading messages" << std::endl; + readMsg(); + } + else if (error != boost::asio::error::operation_aborted) + { + std::cerr << "Connection::handleHandshake: " << error.message() << std::endl; + _connectionManager.stop(shared_from_this()); + } + else + std::cerr << "Handshake error: " << error.message() << std::endl; +} + + +void +Connection::readMsg() +{ + // Read a header first boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(Remote::Header::size); boost::asio::async_read(_socket, @@ -50,13 +73,19 @@ Connection::stop() { if (!_closing) { + boost::system::error_code ec; + _closing = true; std::cout << "Server::Connection::stop, Stopping connection " << this << std::endl; - _socket.close(); + _socket.shutdown(ec); + + if (ec) + std::cerr << "Error while shutting down connection " << this << ": " << ec.message() << std::endl; + std::cout << "Server::Connection::stop, connection stopped " << this << std::endl; } else - std::cout << "Close in progress..." << std::endl; + std::cout << "Stop: close already in progress..." << std::endl; } void @@ -83,7 +112,7 @@ Connection::handleReadHeader(const boost::system::error_code& error, std::size_t return; } - // Now read the real message + // Now read the real message payload boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(header.getDataSize()); boost::asio::async_read(_socket, @@ -178,7 +207,9 @@ Connection::handleReadMsg(const boost::system::error_code& error, std::size_t by _connectionManager.stop(shared_from_this()); } } - start(); + + // All good here, read another message + readMsg(); // Initiate graceful Connection closure. // boost::system::error_code ignored_ec; diff --git a/remote/server/Connection.hpp b/remote/server/Connection.hpp index 8bd23a60..605c02b8 100644 --- a/remote/server/Connection.hpp +++ b/remote/server/Connection.hpp @@ -5,6 +5,7 @@ #include #include +#include #include "RequestHandler.hpp" @@ -20,16 +21,18 @@ class Connection : public std::enable_shared_from_this { public: + typedef boost::asio::ssl::stream ssl_socket; + Connection(const Connection&) = delete; Connection& operator=(const Connection&) = delete; typedef std::shared_ptr pointer; /// Construct a connection with the given io_service. - explicit Connection(boost::asio::ip::tcp::socket socket, + explicit Connection(boost::asio::io_service& ioService, boost::asio::ssl::context& context, ConnectionManager& manager, RequestHandler& handler); - boost::asio::ip::tcp::socket& socket(); + ssl_socket::lowest_layer_type& getSocket() {return _socket.lowest_layer();} /// Start the first asynchronous operation for the connection. void start(); @@ -40,6 +43,12 @@ class Connection : public std::enable_shared_from_this private: bool _closing; + /// Read a new message on the the connection + void readMsg(); + + /// Handle completion of ssl handshake + void handleHandshake(const boost::system::error_code& error); + /// Handle completion of a read operation. void handleReadHeader(const boost::system::error_code& e, std::size_t bytes_transferred); @@ -48,7 +57,7 @@ class Connection : public std::enable_shared_from_this std::size_t bytes_transferred); /// Socket for the connection. - boost::asio::ip::tcp::socket _socket; + ssl_socket _socket; /// The manager for this connection. ConnectionManager& _connectionManager; diff --git a/remote/server/Server.cpp b/remote/server/Server.cpp index 930437a2..51e80752 100644 --- a/remote/server/Server.cpp +++ b/remote/server/Server.cpp @@ -1,6 +1,7 @@ #include +#include #include #include "Server.hpp" @@ -13,9 +14,18 @@ Server::Server(boost::asio::io_service& ioService, const endpoint_type& bindEndp _ioService(ioService), _acceptor(_ioService, bindEndpoint, true /*SO_REUSEADDR*/), _connectionManager(), -_socket(_ioService), +_context(boost::asio::ssl::context::tlsv1_server), _requestHandler(dbPath) { + _context.set_options( boost::asio::ssl::context::default_workarounds // TODO check this thing + | boost::asio::ssl::context::single_dh_use + | boost::asio::ssl::context::no_sslv2 + | boost::asio::ssl::context::no_sslv3 + ); +// context_.set_password_callback(boost::bind(&server::get_password, this)); + _context.use_certificate_chain_file("cert.pem"); // TODO parametrize + _context.use_private_key_file("privkey.pem", boost::asio::ssl::context::pem); // TODO parametrize + _context.use_tmp_dh_file("dh2048.pem"); // TODO parametrize } void @@ -30,12 +40,16 @@ Server::run() void Server::asyncAccept() { - _acceptor.async_accept(_socket, boost::bind(&Server::handleAccept, this, boost::asio::placeholders::error)); + std::shared_ptr newConnection = std::make_shared(_ioService, _context, _connectionManager, _requestHandler); + + _acceptor.async_accept(newConnection->getSocket(), + boost::bind(&Server::handleAccept, this, newConnection, boost::asio::placeholders::error)); } void -Server::handleAccept(boost::system::error_code ec) +Server::handleAccept(std::shared_ptr newConnection, boost::system::error_code ec) { + std::cout << "Server::handleAccept..." << std::endl; // Check whether the server was stopped before this // completion handler had a chance to run. if (!_acceptor.is_open()) @@ -45,8 +59,10 @@ Server::handleAccept(boost::system::error_code ec) if (!ec) { - _connectionManager.start(std::make_shared(std::move(_socket), _connectionManager, _requestHandler)); + _connectionManager.start(newConnection); + // Accept another connection + // TODO: add some limit? asyncAccept(); } else diff --git a/remote/server/Server.hpp b/remote/server/Server.hpp index a0b28cea..e0ead17b 100644 --- a/remote/server/Server.hpp +++ b/remote/server/Server.hpp @@ -2,6 +2,8 @@ #define REMOTE_SERVER_HPP #include +#include + #include #include "Connection.hpp" @@ -32,7 +34,7 @@ class Server private: /// Perform an asynchronous accept operation. void asyncAccept(); - void handleAccept(boost::system::error_code ec); + void handleAccept(std::shared_ptr newConnection, boost::system::error_code ec); boost::asio::io_service& _ioService; @@ -42,8 +44,7 @@ class Server /// The connection manager which owns all live connections. ConnectionManager _connectionManager; - /// The next socket to be accepted. - boost::asio::ip::tcp::socket _socket; + boost::asio::ssl::context _context; /// The handler for all incoming requests. RequestHandler _requestHandler; diff --git a/test/RemoteClientServer.cpp b/test/RemoteClientServer.cpp index 10937604..a5fdc05a 100644 --- a/test/RemoteClientServer.cpp +++ b/test/RemoteClientServer.cpp @@ -11,8 +11,6 @@ #include "remote/messages/Header.hpp" #include "remote/messages/messages.pb.h" -#include "av/Common.hpp" - #include "TestDatabase.hpp" struct GenreInfo @@ -80,44 +78,22 @@ struct Cover }; // Ugly class for testing purposes -class TestServer -{ - public: - TestServer(boost::asio::ip::tcp::endpoint endpoint) - : _db(TestDatabase::create()), - _server(_ioService, endpoint, _db->getPath()), - _thread( boost::bind(&TestServer::threadEntry, this)) - { - _server.run(); - } - - ~TestServer() - { - _server.stop(); - _thread.join(); - } - - private: - - void threadEntry(void) - { - _ioService.run(); - } - - boost::asio::io_service _ioService; - std::unique_ptr _db; - Remote::Server::Server _server; - std::thread _thread; - -}; class TestClient { public: TestClient(boost::asio::ip::tcp::endpoint endpoint) - :_socket(_ioService) + : _context(boost::asio::ssl::context::sslv23), // be large on this + _socket(_ioService, _context) { - _socket.connect(endpoint); + boost::asio::ip::tcp::resolver resolver(_ioService); + + _socket.set_verify_mode(boost::asio::ssl::verify_peer); + _socket.set_verify_callback(boost::bind(&TestClient::verifyCertificate, this, _1, _2)); + + boost::asio::connect(_socket.lowest_layer(), resolver.resolve(endpoint)); + + _socket.handshake(boost::asio::ssl::stream_base::client); } void getArtists(std::vector& artists) @@ -433,6 +409,19 @@ class TestClient private: + bool verifyCertificate(bool preverified, boost::asio::ssl::verify_context& ctx) + { + // In this example we will simply print the certificate's subject name. + std::array subject_name; + + X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle()); + X509_NAME_oneline(X509_get_subject_name(cert), subject_name.data(), subject_name.size()); + + std::cout << "Verifying '" << std::string(subject_name.data()) << "', preverified = " << std::boolalpha << preverified << std::endl; + + return true; // preverified; + } + void mediaAudioPrepare(uint64_t audioId) { @@ -646,11 +635,14 @@ class TestClient } } - boost::asio::io_service _ioService; - boost::asio::ip::tcp::socket _socket; + typedef boost::asio::ssl::stream ssl_socket; - boost::asio::streambuf _inputStreamBuf; - boost::asio::streambuf _outputStreamBuf; + boost::asio::io_service _ioService; + boost::asio::ssl::context _context; + ssl_socket _socket; + + boost::asio::streambuf _inputStreamBuf; + boost::asio::streambuf _outputStreamBuf; }; @@ -658,19 +650,13 @@ class TestClient int main() { try { - // lib init - Av::AvInit(); - Transcode::AvConvTranscoder::init(); - bool extendedTests = true; - // Runs in its own thread - // Listen on any - TestServer testServer( boost::asio::ip::tcp::endpoint( boost::asio::ip::tcp::v4(), 5081)); + std::cout << "Running test... extendedTests = " << std::boolalpha << extendedTests << std::endl; // Client // connect to loopback - TestClient client( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4::loopback(), 5081)); + TestClient client( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4::loopback(), 5080)); // ****** Artists ********* std::vector artists;