diff options
author | Hannes Rantzsch <[email protected]> | 2019-04-24 17:31:54 +0200 |
---|---|---|
committer | Hannes Rantzsch <[email protected]> | 2019-04-24 17:31:54 +0200 |
commit | baa0725609df52014b43bb28297594c7a9c3c320 (patch) | |
tree | 4f02328618472bad3dacf7d019f497f889f732c3 /src/lib/tls | |
parent | cd0580600fd3bbdd49fe60a1333c5ea5df9d5bfc (diff) |
start restructuring asio stream headers
StreamCore is now a nested class of Stream and will soon be hidden from
the public interface. The goal is to offer buffer-handling methods (like
CopyReceivedData) directly in Steam and have StreamCore be responsible
for Botan::TLS::Callbacks implementation only. This will remove the need
to provide StreamCore as a parameter for Async Ops construction.
StreamBase has been removed. Stream no longer decides whether it is a
Client or a Server when constructed, but when performing the handshake.
This resembles the interface of boost::asio::ssl::stream and hides the
implementation detail from the user.
In order to allow testing with mocked TLS::Channels anyways, we use
SPHINAE to setup either a real channel or a mocked channel.
Diffstat (limited to 'src/lib/tls')
-rw-r--r-- | src/lib/tls/asio/asio_async_handshake_op.h | 5 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_async_read_op.h | 5 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_async_write_op.h | 5 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_stream.h | 266 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_stream_base.h | 88 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_stream_core.h | 124 | ||||
-rw-r--r-- | src/lib/tls/asio/info.txt | 2 |
7 files changed, 176 insertions, 319 deletions
diff --git a/src/lib/tls/asio/asio_async_handshake_op.h b/src/lib/tls/asio/asio_async_handshake_op.h index 23875ba7f..f3eea7d16 100644 --- a/src/lib/tls/asio/asio_async_handshake_op.h +++ b/src/lib/tls/asio/asio_async_handshake_op.h @@ -17,7 +17,6 @@ #include <botan/asio_error.h> #include <botan/internal/asio_async_write_op.h> #include <botan/internal/asio_includes.h> -#include <botan/internal/asio_stream_core.h> #include <boost/asio/yield.hpp> @@ -41,7 +40,7 @@ class AsyncHandshakeOperation : public AsyncBase<Handler, typename Stream::execu AsyncHandshakeOperation( HandlerT&& handler, Stream& stream, - StreamCore& core, + typename Stream::StreamCore& core, const boost::system::error_code& ec = {}) : AsyncBase<Handler, typename Stream::executor_type, Allocator>( std::forward<HandlerT>(handler), @@ -118,7 +117,7 @@ class AsyncHandshakeOperation : public AsyncBase<Handler, typename Stream::execu private: Stream& m_stream; - StreamCore& m_core; + typename Stream::StreamCore& m_core; boost::system::error_code m_ec; }; diff --git a/src/lib/tls/asio/asio_async_read_op.h b/src/lib/tls/asio/asio_async_read_op.h index 5902fd388..12f8da8e8 100644 --- a/src/lib/tls/asio/asio_async_read_op.h +++ b/src/lib/tls/asio/asio_async_read_op.h @@ -17,7 +17,6 @@ #include <botan/asio_error.h> #include <botan/internal/asio_async_base.h> #include <botan/internal/asio_includes.h> -#include <botan/internal/asio_stream_core.h> #include <boost/asio/yield.hpp> @@ -41,7 +40,7 @@ class AsyncReadOperation : public AsyncBase<Handler, typename Stream::executor_t template <class HandlerT> AsyncReadOperation(HandlerT&& handler, Stream& stream, - StreamCore& core, + typename Stream::StreamCore& core, const MutableBufferSequence& buffers, const boost::system::error_code& ec = {}) : AsyncBase<Handler, typename Stream::executor_type, Allocator>( @@ -112,7 +111,7 @@ class AsyncReadOperation : public AsyncBase<Handler, typename Stream::executor_t private: Stream& m_stream; - StreamCore& m_core; + typename Stream::StreamCore& m_core; MutableBufferSequence m_buffers; std::size_t m_decodedBytes; diff --git a/src/lib/tls/asio/asio_async_write_op.h b/src/lib/tls/asio/asio_async_write_op.h index db347e5c5..44bd211e3 100644 --- a/src/lib/tls/asio/asio_async_write_op.h +++ b/src/lib/tls/asio/asio_async_write_op.h @@ -16,7 +16,6 @@ #include <botan/internal/asio_async_base.h> #include <botan/internal/asio_includes.h> -#include <botan/internal/asio_stream_core.h> #include <boost/asio/yield.hpp> @@ -43,7 +42,7 @@ class AsyncWriteOperation : public AsyncBase<Handler, typename Stream::executor_ template <class HandlerT> AsyncWriteOperation(HandlerT&& handler, Stream& stream, - StreamCore& core, + typename Stream::StreamCore& core, std::size_t plainBytesTransferred, const boost::system::error_code& ec = {}) : AsyncBase<Handler, typename Stream::executor_type, Allocator>( @@ -89,7 +88,7 @@ class AsyncWriteOperation : public AsyncBase<Handler, typename Stream::executor_ private: Stream& m_stream; - StreamCore& m_core; + typename Stream::StreamCore& m_core; std::size_t m_plainBytesTransferred; boost::system::error_code m_ec; diff --git a/src/lib/tls/asio/asio_stream.h b/src/lib/tls/asio/asio_stream.h index 1654aa658..af257dbd2 100644 --- a/src/lib/tls/asio/asio_stream.h +++ b/src/lib/tls/asio/asio_stream.h @@ -21,10 +21,15 @@ #include <botan/internal/asio_async_read_op.h> #include <botan/internal/asio_async_write_op.h> #include <botan/internal/asio_includes.h> -#include <botan/internal/asio_stream_base.h> -#include <botan/internal/asio_stream_core.h> #include <botan/asio_context.h> +#include <botan/tls_callbacks.h> +#include <botan/tls_channel.h> +#include <botan/tls_client.h> +#include <botan/tls_magic.h> + +#include <boost/beast/core/flat_buffer.hpp> + #include <algorithm> #include <memory> #include <thread> @@ -38,27 +43,30 @@ namespace TLS { * boost::asio compatible SSL/TLS stream * * Currently only the TLS::Client specialization is implemented. + * + * \tparam StreamLayer type of the next layer, usually a network socket + * \tparam ChannelT type of the native_handle, defaults to Botan::TLS::Channel, only needed for testing purposes */ -template <class StreamLayer, class Channel> -class Stream : public StreamBase<Channel> +template <class StreamLayer, class ChannelT = Channel> +class Stream { public: using next_layer_type = typename std::remove_reference<StreamLayer>::type; using lowest_layer_type = typename next_layer_type::lowest_layer_type; using executor_type = typename next_layer_type::executor_type; - using native_handle_type = typename std::add_pointer<Channel>::type; - - using StreamBase<Channel>::validate_connection_side; + using native_handle_type = typename std::add_pointer<ChannelT>::type; public: template <typename... Args> explicit Stream(Context& context, Args&& ... args) - : StreamBase<Channel>(context), m_nextLayer(std::forward<Args>(args)...) {} + : m_context(context), m_nextLayer(std::forward<Args>(args)...) {} // overload for boost::asio::ssl::stream compatibility template <typename Arg> explicit Stream(Arg&& arg, Context& context) - : StreamBase<Channel>(context), m_nextLayer(std::forward<Arg>(arg)) {} + : m_context(context), m_nextLayer(std::forward<Arg>(arg)) {} + + virtual ~Stream() = default; Stream(Stream&& other) = default; Stream& operator=(Stream&& other) = default; @@ -78,7 +86,7 @@ class Stream : public StreamBase<Channel> lowest_layer_type& lowest_layer() { return m_nextLayer.lowest_layer(); } const lowest_layer_type& lowest_layer() const { return m_nextLayer.lowest_layer(); } - native_handle_type native_handle() { return &this->m_channel; } + native_handle_type native_handle() { return m_channel.get(); } // // -- -- configuration and callback setters @@ -155,22 +163,27 @@ class Stream : public StreamBase<Channel> /** * Performs SSL handshaking. * The function call will block until handshaking is complete or an error occurs. + * @param type The type of handshaking to be performed, i.e. as a client or as a server. * @throws boost::system::system_error if error occured + * @throws Invalid_Argument if Connection_Side could not be validated */ - void handshake() + void handshake(Connection_Side side) { boost::system::error_code ec; - handshake(ec); + handshake(side, ec); boost::asio::detail::throw_error(ec, "handshake"); } /** * Performs SSL handshaking. * The function call will block until handshaking is complete or an error occurs. + * @param type The type of handshaking to be performed, i.e. as a client or as a server. * @param ec Set to indicate what error occurred, if any. */ - void handshake(boost::system::error_code& ec) + void handshake(Connection_Side side, boost::system::error_code& ec) { + setup_channel(side); + while(!native_handle()->is_active()) { sendPendingEncryptedData(ec); @@ -201,7 +214,7 @@ class Stream : public StreamBase<Channel> ec = e.error_type(); return; } - catch(const std::exception &) + catch(const std::exception&) { ec = Botan::ErrorType::Unknown; return; @@ -214,16 +227,20 @@ class Stream : public StreamBase<Channel> /** * Starts an asynchronous SSL handshake. * This function call always returns immediately. + * @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 */ template <typename HandshakeHandler> BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, void(boost::system::error_code)) - async_handshake(HandshakeHandler&& handler) + async_handshake(Connection_Side side, HandshakeHandler&& handler) { BOOST_ASIO_HANDSHAKE_HANDLER_CHECK(HandshakeHandler, handler) type_check; + setup_channel(side); + boost::asio::async_completion<HandshakeHandler, void(boost::system::error_code)> init(handler); AsyncHandshakeOperation<typename std::decay<HandshakeHandler>::type, Stream> @@ -232,83 +249,6 @@ class Stream : public StreamBase<Channel> return init.result.get(); } - // - // -- -- asio::ssl::stream compatibility methods - // - // The OpenSSL-based stream contains an operation flag that tells - // the stream to either impersonate a TLS server or client. This - // implementation defines those modes at compile time (via template - // specialization of the StreamBase class) and merely checks the - // flag's consistency before performing the respective handshakes. - // - - /** - * Performs SSL handshaking. - * The function call will block until handshaking is complete or an error occurs. - * @param type The type of handshaking to be performed, i.e. as a client or as a server. - * @throws boost::system::system_error if error occured - * @throws Invalid_Argument if Connection_Side could not be validated - */ - void handshake(Connection_Side side) - { - validate_connection_side(side); - handshake(); - } - - /** - * Performs SSL handshaking. - * The function call will block until handshaking is complete or an error occurs. - * @param type The type of handshaking to be performed, i.e. as a client or as a server. - * @param ec Set to indicate what error occurred, if any. - */ - void handshake(Connection_Side side, boost::system::error_code& ec) - { - if(validate_connection_side(side, ec)) - { handshake(ec); } - } - - /** - * Starts an asynchronous SSL handshake. - * This function call always returns immediately. - * @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 - */ - template <typename HandshakeHandler> - BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, - void(boost::system::error_code)) - async_handshake(Connection_Side side, HandshakeHandler&& handler) - { - validate_connection_side(side); - return async_handshake(std::forward<HandshakeHandler>(handler)); - } - - /** - * @throws Not_Implemented - */ - template<typename ConstBufferSequence> - void handshake(Connection_Side side, const ConstBufferSequence& buffers) - { - BOTAN_UNUSED(buffers); - validate_connection_side(side); - throw Not_Implemented("buffered handshake is not implemented"); - } - - /** - * Not Implemented. - * @param ec Will be set to `Botan::ErrorType::NotImplemented` - */ - template<typename ConstBufferSequence> - void handshake(Connection_Side side, - const ConstBufferSequence& buffers, - boost::system::error_code& ec) - { - BOTAN_UNUSED(buffers); - if(validate_connection_side(side, ec)) - { ec = Botan::ErrorType::NotImplemented; } - } - /** * @throws Not_Implemented */ @@ -349,7 +289,7 @@ class Stream : public StreamBase<Channel> ec = e.error_type(); return; } - catch(const std::exception &) + catch(const std::exception&) { ec = Botan::ErrorType::Unknown; return; @@ -527,7 +467,138 @@ class Stream : public StreamBase<Channel> return init.result.get(); } + // TODO: remove StreamCore from public interface + /** + * Contains the buffers for reading/sending, and the needed botan callbacks + */ + class StreamCore : public Botan::TLS::Callbacks + { + public: + StreamCore() + : m_input_buffer_space(MAX_CIPHERTEXT_SIZE, '\0'), + input_buffer(m_input_buffer_space.data(), m_input_buffer_space.size()) {} + + virtual ~StreamCore() = default; + + void tls_emit_data(const uint8_t data[], std::size_t size) override + { + // Provide the encrypted TLS data in the sendBuffer. Actually sending the data is done + // using (async_)write_some either in the stream or in an async operation. + m_send_buffer.commit( + boost::asio::buffer_copy(m_send_buffer.prepare(size), boost::asio::buffer(data, size))); + } + + void tls_record_received(uint64_t, const uint8_t data[], std::size_t size) override + { + // TODO: It would be nice to avoid this buffer copy. However, we need to deal with the case + // that the receive buffer provided by the caller is smaller than the decrypted record. + auto buffer = m_receive_buffer.prepare(size); + auto copySize = + boost::asio::buffer_copy(buffer, boost::asio::const_buffer(data, size)); + m_receive_buffer.commit(copySize); + } + + void tls_alert(Botan::TLS::Alert alert) override + { + if(alert.type() == Botan::TLS::Alert::CLOSE_NOTIFY) + { + // TODO + } + } + + std::chrono::milliseconds tls_verify_cert_chain_ocsp_timeout() const override + { + return std::chrono::milliseconds(1000); + } + + bool tls_session_established(const Botan::TLS::Session&) override + { + // TODO: it should be possible to configure this in the using application (via callback?) + return true; + } + + bool hasReceivedData() const + { + return m_receive_buffer.size() > 0; + } + + template <typename MutableBufferSequence> + std::size_t copyReceivedData(MutableBufferSequence buffers) + { + const auto copiedBytes = + boost::asio::buffer_copy(buffers, m_receive_buffer.data()); + m_receive_buffer.consume(copiedBytes); + return copiedBytes; + } + + bool hasDataToSend() const { return m_send_buffer.size() > 0; } + + boost::asio::const_buffer sendBuffer() const + { + return m_send_buffer.data(); + } + + void consumeSendBuffer(std::size_t bytesConsumed) + { + m_send_buffer.consume(bytesConsumed); + } + + void clearSendBuffer() + { + consumeSendBuffer(m_send_buffer.size()); + } + + private: + // Buffer space used to read input intended for the engine. + std::vector<uint8_t> m_input_buffer_space; + boost::beast::flat_buffer m_receive_buffer; + boost::beast::flat_buffer m_send_buffer; + + public: + // A buffer that may be used to read input intended for the engine. + const boost::asio::mutable_buffer input_buffer; + }; + protected: + // TODO: explain, note: c++17 makes this much better with constexpr if + template<class T = ChannelT> + typename std::enable_if<!std::is_same<Channel, T>::value>::type + setup_channel(Connection_Side) {} + + template<class T = ChannelT> + typename std::enable_if<std::is_same<Channel, T>::value>::type + setup_channel(Connection_Side side) + { + assert(side == CLIENT); + m_channel = std::unique_ptr<Client>(new Client(m_core, + *m_context.sessionManager, + *m_context.credentialsManager, + *m_context.policy, + *m_context.randomNumberGenerator, + m_context.serverInfo)); + } + + //! \brief validate the connection side (OpenSSL compatibility) + void validate_connection_side(Connection_Side side) + { + if(side != CLIENT) + { + throw Invalid_Argument("wrong connection_side"); + } + } + + //! \brief validate the connection side (OpenSSL compatibility) + bool validate_connection_side(Connection_Side side, boost::system::error_code& ec) + { + if(side != CLIENT) + { + ec = Botan::ErrorType::InvalidArgument; + return false; + } + + return true; + } + size_t sendPendingEncryptedData(boost::system::error_code& ec) { auto writtenBytes = boost::asio::write(m_nextLayer, this->m_core.sendBuffer(), ec); @@ -562,7 +633,7 @@ class Stream : public StreamBase<Channel> ec = e.error_type(); return; } - catch(const std::exception &) + catch(const std::exception&) { ec = Botan::ErrorType::Unknown; return; @@ -601,7 +672,7 @@ class Stream : public StreamBase<Channel> ec = e.error_type(); return 0; } - catch(const std::exception &) + catch(const std::exception&) { ec = Botan::ErrorType::Unknown; return 0; @@ -612,7 +683,10 @@ class Stream : public StreamBase<Channel> return sent; } - StreamLayer m_nextLayer; + Context m_context; + StreamLayer m_nextLayer; + StreamCore m_core; + std::unique_ptr<ChannelT> m_channel; }; } // namespace TLS diff --git a/src/lib/tls/asio/asio_stream_base.h b/src/lib/tls/asio/asio_stream_base.h deleted file mode 100644 index 9a9cc568b..000000000 --- a/src/lib/tls/asio/asio_stream_base.h +++ /dev/null @@ -1,88 +0,0 @@ -/* -* TLS Stream Helper -* (C) 2018-2019 Jack Lloyd -* 2018-2019 Hannes Rantzsch, Tim Oesterreich, Rene Meusel -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_ASIO_STREAM_BASE_H_ -#define BOTAN_ASIO_STREAM_BASE_H_ - -#include <botan/build.h> - -#include <boost/version.hpp> -#if BOOST_VERSION >= 106600 - -#include <botan/asio_context.h> -#include <botan/asio_error.h> -#include <botan/internal/asio_stream_core.h> -#include <botan/tls_client.h> -#include <botan/tls_magic.h> - -namespace Botan { - -namespace TLS { - -/** Base class for all Botan::TLS::Stream implementations. - * - * This template must be specialized for all the Botan::TLS::Channel to be used. - * Currently it only supports the Botan::TLS::Client channel that impersonates - * the client-side of a TLS connection. - * - * TODO: create a Botan::TLS::Server specialization - */ -template <class Channel> -class StreamBase - { - }; - -template <> -class StreamBase<Botan::TLS::Client> - { - public: - StreamBase(Context& context) - : m_channel(m_core, - *context.sessionManager, - *context.credentialsManager, - *context.policy, - *context.randomNumberGenerator, - context.serverInfo) - { - } - - StreamBase(const StreamBase&) = delete; - StreamBase& operator=(const StreamBase&) = delete; - - protected: - //! \brief validate the connection side (OpenSSL compatibility) - void validate_connection_side(Connection_Side side) - { - if(side != CLIENT) - { - throw Invalid_Argument("wrong connection_side"); - } - } - - //! \brief validate the connection side (OpenSSL compatibility) - bool validate_connection_side(Connection_Side side, boost::system::error_code& ec) - { - if(side != CLIENT) - { - ec = Botan::ErrorType::InvalidArgument; - return false; - } - - return true; - } - - Botan::TLS::StreamCore m_core; - Botan::TLS::Client m_channel; - }; - -} // namespace TLS - -} // namespace Botan - -#endif // BOOST_VERSION -#endif // BOTAN_ASIO_STREAM_BASE_H_ diff --git a/src/lib/tls/asio/asio_stream_core.h b/src/lib/tls/asio/asio_stream_core.h deleted file mode 100644 index dbf95c37a..000000000 --- a/src/lib/tls/asio/asio_stream_core.h +++ /dev/null @@ -1,124 +0,0 @@ -/* -* TLS Stream Helper -* (C) 2018-2019 Jack Lloyd -* 2018-2019 Hannes Rantzsch, Tim Oesterreich, Rene Meusel -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_ASIO_STREAM_CORE_H_ -#define BOTAN_ASIO_STREAM_CORE_H_ - -#include <botan/build.h> - -#include <boost/version.hpp> -#if BOOST_VERSION >= 106600 - -#include <boost/beast/core/flat_buffer.hpp> -#include <botan/internal/asio_includes.h> -#include <botan/tls_callbacks.h> -#include <botan/tls_magic.h> -#include <mutex> -#include <vector> - -namespace Botan { - -namespace TLS { - -/** - * Contains the buffers for reading/sending, and the needed botan callbacks - */ -class StreamCore : public Botan::TLS::Callbacks - { - public: - StreamCore() - : m_input_buffer_space(MAX_CIPHERTEXT_SIZE, '\0'), - input_buffer(m_input_buffer_space.data(), m_input_buffer_space.size()) {} - - virtual ~StreamCore() = default; - - void tls_emit_data(const uint8_t data[], std::size_t size) override - { - // Provide the encrypted TLS data in the sendBuffer. Actually sending the data is done - // using (async_)write_some either in the stream or in an async operation. - m_send_buffer.commit( - boost::asio::buffer_copy(m_send_buffer.prepare(size), boost::asio::buffer(data, size))); - } - - void tls_record_received(uint64_t, const uint8_t data[], std::size_t size) override - { - // TODO: It would be nice to avoid this buffer copy. However, we need to deal with the case - // that the receive buffer provided by the caller is smaller than the decrypted record. - auto buffer = m_receive_buffer.prepare(size); - auto copySize = - boost::asio::buffer_copy(buffer, boost::asio::const_buffer(data, size)); - m_receive_buffer.commit(copySize); - } - - void tls_alert(Botan::TLS::Alert alert) override - { - if(alert.type() == Botan::TLS::Alert::CLOSE_NOTIFY) - { - // TODO - } - } - - std::chrono::milliseconds tls_verify_cert_chain_ocsp_timeout() const override - { - return std::chrono::milliseconds(1000); - } - - bool tls_session_established(const Botan::TLS::Session&) override - { - // TODO: it should be possible to configure this in the using application (via callback?) - return true; - } - - bool hasReceivedData() const - { - return m_receive_buffer.size() > 0; - } - - template <typename MutableBufferSequence> - std::size_t copyReceivedData(MutableBufferSequence buffers) - { - const auto copiedBytes = - boost::asio::buffer_copy(buffers, m_receive_buffer.data()); - m_receive_buffer.consume(copiedBytes); - return copiedBytes; - } - - bool hasDataToSend() const { return m_send_buffer.size() > 0; } - - boost::asio::const_buffer sendBuffer() const - { - return m_send_buffer.data(); - } - - void consumeSendBuffer(std::size_t bytesConsumed) - { - m_send_buffer.consume(bytesConsumed); - } - - void clearSendBuffer() - { - consumeSendBuffer(m_send_buffer.size()); - } - - private: - // Buffer space used to read input intended for the engine. - std::vector<uint8_t> m_input_buffer_space; - boost::beast::flat_buffer m_receive_buffer; - boost::beast::flat_buffer m_send_buffer; - - public: - // A buffer that may be used to read input intended for the engine. - const boost::asio::mutable_buffer input_buffer; - }; - -} // namespace TLS - -} // namespace Botan - -#endif // BOOST_VERSION -#endif // BOTAN_ASIO_STREAM_CORE_H_ diff --git a/src/lib/tls/asio/info.txt b/src/lib/tls/asio/info.txt index 075ecb795..1e37ea2a3 100644 --- a/src/lib/tls/asio/info.txt +++ b/src/lib/tls/asio/info.txt @@ -13,8 +13,6 @@ asio_async_base.h asio_async_handshake_op.h asio_async_read_op.h asio_async_write_op.h -asio_stream_base.h -asio_stream_core.h asio_includes.h </header:internal> |