diff options
-rw-r--r-- | doc/authors.txt | 1 | ||||
-rw-r--r-- | doc/manual/tls.rst | 242 | ||||
-rw-r--r-- | readme.rst | 1 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_stream.h | 2 |
4 files changed, 245 insertions, 1 deletions
diff --git a/doc/authors.txt b/doc/authors.txt index 7f5f90d0b..abaaab024 100644 --- a/doc/authors.txt +++ b/doc/authors.txt @@ -35,6 +35,7 @@ Frank Schoenmann Google Inc Gustavo Serra Scalet guywithcrookedface +Hannes Rantzsch Harry Reimann Hegedüs Márton Csaba Hubert Bugaj diff --git a/doc/manual/tls.rst b/doc/manual/tls.rst index bb363ca43..164b6b8d6 100644 --- a/doc/manual/tls.rst +++ b/doc/manual/tls.rst @@ -1600,3 +1600,245 @@ Server Code Example // send data to the tls client using server.send_data() // ... } + +TLS Stream +---------------------------------------- + +:cpp:class:`TLS::Stream` offers a Boost.Asio compatible wrapper around :cpp:class:`TLS::Client`. +It can be used as an alternative to Boost.Asio's `ssl::stream <https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/reference/ssl__stream.html>`_ with minor adjustments to the using code. +It offers the following interface: + +.. cpp:class:: template <class StreamLayer, class ChannelT> TLS::Stream + + *StreamLayer* specifies the type of the stream's *next layer*, for example a `Boost.Asio TCP socket <https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/reference/ip__tcp/socket.html>`_. + *ChannelT* is the type of the stream's *native handle*; it defaults to :cpp:class:`TLS::Channel` and should not be specified manually. + + .. cpp:function:: template <typename... Args> \ + explicit Stream(Context& context, Args&& ... args) + + Construct a new TLS stream. + The *context* parameter will be used to set up the underlying *native handle*, i.e. the :ref:`TLS::Client <tls_client>`, when :cpp:func:`handshake` is called. + The further *args* will be forwarded to the *next layer*'s constructor. + + .. cpp:function:: template <typename... Args> \ + explicit Stream(Arg&& arg, Context& context) + + Convenience constructor for :cpp:class:`boost::asio::ssl::stream` compatibility. + The parameters have the same meaning as for the first constructor, but their order is changed and only one argument can be passed to the *next layer* constructor. + + + .. cpp:function:: void handshake(Connection_Side side, boost::system::error_code& ec) + + Set up the *native handle* and perform the TLS handshake. + As only the client side of the stream is currently implemented, *side* should be ``Connection_Side::CLIENT``. + + .. cpp:function:: void handshake(Connection_Side side) + + Overload of :cpp:func:`handshake` that throws an exception if an error occurs. + + .. cpp:function:: template <typename HandshakeHandler> \ + DEDUCED async_handshake(Connection_Side side, HandshakeHandler&& handler) + + Asynchronous variant of :cpp:func:`handshake`. + The function returns immediately and calls the *handler* callback function after performing asynchronous I/O to complete the TLS handshake. + The return type is an automatically deduced specialization of :cpp:class:`boost::asio::async_result`, depending on the *HandshakeHandler* type. + + + .. cpp:function:: void shutdown(boost::system::error_code& ec) + + Calls :ref:`close <TLS::Channel::close>` on the native handle and writes the TLS alert to the *next layer*. + + .. cpp:function:: void shutdown() + + Overload of :cpp:func:`shutdown` that throws an exception if an error occurs. + + + .. cpp:function:: template <typename MutableBufferSequence> \ + std::size_t read_some(const MutableBufferSequence& buffers, boost::system::error_code& ec) + + Reads encrypted data from the *next layer*, decrypts it, and writes it into the provided *buffers*. + If an error occurs, *error_code* is set. + Returns the number of bytes read. + + .. cpp:function:: template <typename MutableBufferSequence> \ + std::size_t read_some(const MutableBufferSequence& buffers) + + Overload of :cpp:func:`read_some` that throws an exception if an error occurs. + + .. cpp:function:: template <typename MutableBufferSequence, typename ReadHandler> \ + DEDUCED async_read_some(const MutableBufferSequence& buffers, ReadHandler&& handler) + + Asynchronous variant of :cpp:func:`read_some`. + The function returns immediately and calls the *handler* callback function after writing the decrypted data into the provided *buffers*. + The return type is an automatically deduced specialization of :cpp:class:`boost::asio::async_result`, depending on the *ReadHandler* type. + *ReadHandler* should suffice the `requirements to a Boost.Asio read handler <https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/reference/ReadHandler.html>`_. + + + .. cpp:function:: template <typename ConstBufferSequence> \ + std::size_t write_some(const ConstBufferSequence& buffers, boost::system::error_code& ec) + + Encrypts data from the provided *buffers* and writes it to the *next layer*. + If an error occurs, *error_code* is set. + Returns the number of bytes written. + + .. cpp:function:: template <typename ConstBufferSequence> \ + std::size_t write_some(const ConstBufferSequence& buffers) + + Overload of :cpp:func:`write_some` that throws an exception rather than setting an error code. + + .. cpp:function:: template <typename ConstBufferSequence, typename WriteHandler> \ + DEDUCED async_write_some(const ConstBufferSequence& buffers, WriteHandler&& handler) + + Asynchronous variant of :cpp:func:`write_some`. + The function returns immediately and calls the *handler* callback function after writing the encrypted data to the *next layer*. + The return type is an automatically deduced specialization of :cpp:class:`boost::asio::async_result`, depending on the *WriteHandler* type. + *WriteHandler* should suffice the `requirements to a Boost.Asio write handler <https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/reference/WriteHandler.html>`_. + +.. cpp:struct:: TLS::Context + + A helper struct to collect the initialization parameters for the Stream's underlying *native handle* (see :cpp:func:`TLS::Client`). + `TLS::Context` is defined as + + .. code-block:: cpp + + struct Context + { + Credentials_Manager* credentialsManager; + RandomNumberGenerator* randomNumberGenerator; + Session_Manager* sessionManager; + Policy* policy; + Server_Information serverInfo; + }; + + +Stream Code Example +^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: cpp + + #include <iostream> + + #include <botan/asio_stream.h> + #include <botan/auto_rng.h> + #include <botan/certstor_system.h> + + #include <boost/asio.hpp> + #include <boost/beast.hpp> + #include <boost/bind.hpp> + + namespace http = boost::beast::http; + namespace _ = boost::asio::placeholders; + + // very basic credentials manager + class Credentials_Manager : public Botan::Credentials_Manager + { + public: + Credentials_Manager() {} + + std::vector<Botan::Certificate_Store*> + trusted_certificate_authorities(const std::string&, const std::string&) override + { + return {&cert_store_}; + } + + private: + Botan::System_Certificate_Store cert_store_; + }; + + // a simple https client based on TLS::Stream + class client + { + public: + client(boost::asio::io_context& io_context, + boost::asio::ip::tcp::resolver::iterator endpoint_iterator, + http::request<http::string_body> req) + : request_(req) + , ctx_{&credentials_mgr_, + &rng_, + &session_mgr_, + &policy_, + Botan::TLS::Server_Information()} + , stream_(io_context, ctx_) + { + boost::asio::async_connect(stream_.lowest_layer(), endpoint_iterator, + boost::bind(&client::handle_connect, this, _::error)); + } + + void handle_connect(const boost::system::error_code& error) + { + if(error) + { + std::cout << "Connect failed: " << error.message() << "\n"; + return; + } + stream_.async_handshake(Botan::TLS::Connection_Side::CLIENT, + boost::bind(&client::handle_handshake, this, _::error)); + } + + void handle_handshake(const boost::system::error_code& error) + { + if(error) + { + std::cout << "Handshake failed: " << error.message() << "\n"; + return; + } + http::async_write(stream_, request_, + boost::bind(&client::handle_write, this, _::error, _::bytes_transferred)); + } + + void handle_write(const boost::system::error_code& error, size_t) + { + if(error) + { + std::cout << "Write failed: " << error.message() << "\n"; + return; + } + http::async_read(stream_, reply_, response_, + boost::bind(&client::handle_read, this, _::error, _::bytes_transferred)); + } + + void handle_read(const boost::system::error_code& error, size_t) + { + if(!error) + { + std::cout << "Reply: "; + std::cout << response_.body() << "\n"; + } + else + { + std::cout << "Read failed: " << error.message() << "\n"; + } + } + + private: + http::request<http::dynamic_body> request_; + http::response<http::string_body> response_; + boost::beast::flat_buffer reply_; + + Botan::TLS::Session_Manager_Noop session_mgr_; + Botan::AutoSeeded_RNG rng_; + Credentials_Manager credentials_mgr_; + Botan::TLS::Policy policy_; + + Botan::TLS::Context ctx_; + Botan::TLS::Stream<boost::asio::ip::tcp::socket> stream_; + }; + + int main() + { + boost::asio::io_context io_context; + + boost::asio::ip::tcp::resolver resolver(io_context); + boost::asio::ip::tcp::resolver::query query("botan.randombit.net", "443"); + boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query); + + http::request<http::string_body> req; + req.version(11); + req.method(http::verb::get); + req.target("/news.html"); + req.set(http::field::host, "botan.randombit.net"); + + client c(io_context, iterator, req); + + io_context.run(); + } diff --git a/readme.rst b/readme.rst index 1c5855c2c..dd7876368 100644 --- a/readme.rst +++ b/readme.rst @@ -119,3 +119,4 @@ Other Useful Things * Format preserving encryption scheme FE1 * Threshold secret sharing * NIST key wrapping +* Boost.Asio compatible TLS client stream diff --git a/src/lib/tls/asio/asio_stream.h b/src/lib/tls/asio/asio_stream.h index c0f8c73f4..7cdd8d3dd 100644 --- a/src/lib/tls/asio/asio_stream.h +++ b/src/lib/tls/asio/asio_stream.h @@ -229,7 +229,7 @@ class Stream * @param type The type of handshaking to be performed, i.e. as a client or as a server. * @param handler The handler to be called when the handshake operation completes. * The equivalent function signature of the handler must be: void(boost::system::error_code) - * @throws Invalid_Argument if Connection_Side could not be validated + * @throws NotImplemented if Connection_Side is not CLIENT */ template <typename HandshakeHandler> BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, |