diff options
-rw-r--r-- | src/lib/tls/asio/asio_async_base.h | 109 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_async_handshake_op.h | 129 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_async_ops.h | 368 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_async_read_op.h | 124 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_async_write_op.h | 99 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_error.h | 22 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_includes.h | 25 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_stream.h | 188 | ||||
-rw-r--r-- | src/lib/tls/asio/info.txt | 9 | ||||
-rw-r--r-- | src/tests/unit_asio_stream.cpp | 6 |
10 files changed, 476 insertions, 603 deletions
diff --git a/src/lib/tls/asio/asio_async_base.h b/src/lib/tls/asio/asio_async_base.h deleted file mode 100644 index 6585ded55..000000000 --- a/src/lib/tls/asio/asio_async_base.h +++ /dev/null @@ -1,109 +0,0 @@ -/* -* TLS ASIO 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_ASYNC_BASE_H_ -#define BOTAN_ASIO_ASYNC_BASE_H_ - -#include <botan/build.h> - -#include <boost/version.hpp> -#if BOOST_VERSION >= 106600 - -#include <botan/internal/asio_includes.h> - -namespace Botan { - -namespace TLS { - -/** - * Base class for asynchronous stream operations. - * - * Asynchronous operations, used for example to implement an interface for boost::asio::async_read_some and - * boost::asio::async_write_some, are based on boost::asio::coroutines. - * Derived operations should implement a call operator and invoke it with the correct parameters upon construction. The - * call operator needs to make sure that the user-provided handler is not called directly. Typically, yield / reenter is - * used for this in the following fashion: - * - * ``` - * void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true) - * { - * reenter(this) - * { - * // operation specific logic, repeatedly interacting with the stream_core and the next_layer (socket) - * - * // make sure intermediate initiating function is called - * if(!isContinuation) - * { - * yield next_layer.async_operation(empty_buffer, this); - * } - * - * // call the completion handler - * complete_now(error_code, bytes_transferred); - * } - * } - * ``` - * - * Once the operation is completed and ready to call the completion handler it checks if an intermediate initiating - * function has been called using the `isContinuation` parameter. If not, it will call an asynchronous operation, such - * as `async_read_some`, with and empty buffer, set the object itself as the handler, and `yield`. As a result, the call - * operator will be invoked again, this time as a continuation, and will jump to the location where it yielded before - * using `reenter`. It is now safe to call the handler function via `complete_now`. - * - * \tparam Handler Type of the completion handler - * \tparam Executor1 Type of the asio executor (usually derived from the lower layer) - * \tparam Allocator Type of the allocator to be used - */ -template <class Handler, class Executor1, class Allocator> -class AsyncBase : public boost::asio::coroutine - { - public: - using allocator_type = boost::asio::associated_allocator_t<Handler, Allocator>; - using executor_type = boost::asio::associated_executor_t<Handler, Executor1>; - - allocator_type get_allocator() const noexcept - { - return boost::asio::get_associated_allocator(m_handler); - } - - executor_type get_executor() const noexcept - { - return boost::asio::get_associated_executor(m_handler, m_work_guard_1.get_executor()); - } - - protected: - template <class HandlerT> - AsyncBase(HandlerT&& handler, const Executor1& executor) - : m_handler(std::forward<HandlerT>(handler)) - , m_work_guard_1(executor) - { - } - - /** - * Call the completion handler. - * - * This function should only be called after an intermediate initiating function has been called. - * - * @param args Arguments forwarded to the completion handler function. - */ - template<class... Args> - void complete_now(Args&& ... args) - { - m_work_guard_1.reset(); - m_handler(std::forward<Args>(args)...); - } - - Handler m_handler; - boost::asio::executor_work_guard<Executor1> m_work_guard_1; - }; - -} // namespace TLS - -} // namespace Botan - -#endif // BOOST_VERSION -#endif // BOTAN_ASIO_ASYNC_BASE_H_ diff --git a/src/lib/tls/asio/asio_async_handshake_op.h b/src/lib/tls/asio/asio_async_handshake_op.h deleted file mode 100644 index e209c91da..000000000 --- a/src/lib/tls/asio/asio_async_handshake_op.h +++ /dev/null @@ -1,129 +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_ASYNC_HANDSHAKE_OP_H_ -#define BOTAN_ASIO_ASYNC_HANDSHAKE_OP_H_ - -#include <botan/build.h> - -#include <boost/version.hpp> -#if BOOST_VERSION >= 106600 - -#include <botan/asio_error.h> -#include <botan/internal/asio_async_write_op.h> -#include <botan/internal/asio_includes.h> - -#include <boost/asio/yield.hpp> - -namespace Botan { - -namespace TLS { - -template <class Handler, class Stream, class Allocator = std::allocator<void>> -class AsyncHandshakeOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator> - { - public: - /** - * Construct and invoke an AsyncHandshakeOperation. - * - * @param handler Handler function to be called upon completion. - * @param stream The stream from which the data will be read - * @param ec Optional error code; used to report an error to the handler function. - */ - template<class HandlerT> - AsyncHandshakeOperation( - HandlerT&& handler, - Stream& stream, - const boost::system::error_code& ec = {}) - : AsyncBase<Handler, typename Stream::executor_type, Allocator>( - std::forward<HandlerT>(handler), - stream.get_executor()) - , m_stream(stream) - { - this->operator()(ec, std::size_t(0), false); - } - - AsyncHandshakeOperation(AsyncHandshakeOperation&&) = default; - - void operator()(boost::system::error_code ec, std::size_t bytesTransferred, bool isContinuation = true) - { - reenter(this) - { - if(bytesTransferred > 0 && !ec) - { - // Provide encrypted TLS data received from the network to TLS::Channel for decryption - boost::asio::const_buffer read_buffer {m_stream.input_buffer().data(), bytesTransferred}; - try - { - m_stream.native_handle()->received_data( - static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size() - ); - } - catch(const TLS_Exception& e) - { - ec = e.type(); - } - catch(const Botan::Exception& e) - { - ec = e.error_type(); - } - catch(...) - { - ec = Botan::ErrorType::Unknown; - } - } - - if(m_stream.hasDataToSend() && !ec) - { - // Write encrypted TLS data provided by the TLS::Channel on the wire - - // Note: we construct `AsyncWriteOperation` with 0 as its last parameter (`plainBytesTransferred`). This - // operation will eventually call `*this` as its own handler, passing the 0 back to this call operator. - // This is necessary because the check of `bytesTransferred > 0` assumes that `bytesTransferred` bytes - // were just read and are available in input_buffer for further processing. - AsyncWriteOperation< - AsyncHandshakeOperation<typename std::decay<Handler>::type, Stream, Allocator>, - Stream, - Allocator> - op{std::move(*this), m_stream, 0}; - return; - } - - if(!m_stream.native_handle()->is_active() && !ec) - { - // Read more encrypted TLS data from the network - m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this)); - return; - } - - if(!isContinuation) - { - // Make sure the handler is not called without an intermediate initiating function. - // "Reading" into a zero-byte buffer will complete immediately. - m_ec = ec; - yield m_stream.next_layer().async_read_some(boost::asio::mutable_buffer(), std::move(*this)); - ec = m_ec; - } - - this->complete_now(ec); - } - } - - private: - Stream& m_stream; - boost::system::error_code m_ec; - }; - -} // namespace TLS - -} // namespace Botan - -#include <boost/asio/unyield.hpp> - -#endif // BOOST_VERSION -#endif // BOTAN_ASIO_ASYNC_HANDSHAKE_OP_H_ diff --git a/src/lib/tls/asio/asio_async_ops.h b/src/lib/tls/asio/asio_async_ops.h new file mode 100644 index 000000000..c94e48fd3 --- /dev/null +++ b/src/lib/tls/asio/asio_async_ops.h @@ -0,0 +1,368 @@ +/* +* Helpers for TLS ASIO Stream +* (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_ASYNC_OPS_H_ +#define BOTAN_ASIO_ASYNC_OPS_H_ + +#include <botan/build.h> + +#include <boost/version.hpp> +#if BOOST_VERSION >= 106600 + +#include <botan/asio_error.h> + +// We need to define BOOST_ASIO_DISABLE_SERIAL_PORT before any asio imports. Otherwise asio will include <termios.h>, +// which interferes with Botan's amalgamation by defining macros like 'B0' and 'FF1'. +#define BOOST_ASIO_DISABLE_SERIAL_PORT +#include <boost/asio.hpp> +#include <boost/asio/yield.hpp> + +namespace Botan { +namespace TLS { +namespace detail { + +/** + * Base class for asynchronous stream operations. + * + * Asynchronous operations, used for example to implement an interface for boost::asio::async_read_some and + * boost::asio::async_write_some, are based on boost::asio::coroutines. + * Derived operations should implement a call operator and invoke it with the correct parameters upon construction. The + * call operator needs to make sure that the user-provided handler is not called directly. Typically, yield / reenter is + * used for this in the following fashion: + * + * ``` + * void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true) + * { + * reenter(this) + * { + * // operation specific logic, repeatedly interacting with the stream_core and the next_layer (socket) + * + * // make sure intermediate initiating function is called + * if(!isContinuation) + * { + * yield next_layer.async_operation(empty_buffer, this); + * } + * + * // call the completion handler + * complete_now(error_code, bytes_transferred); + * } + * } + * ``` + * + * Once the operation is completed and ready to call the completion handler it checks if an intermediate initiating + * function has been called using the `isContinuation` parameter. If not, it will call an asynchronous operation, such + * as `async_read_some`, with and empty buffer, set the object itself as the handler, and `yield`. As a result, the call + * operator will be invoked again, this time as a continuation, and will jump to the location where it yielded before + * using `reenter`. It is now safe to call the handler function via `complete_now`. + * + * \tparam Handler Type of the completion handler + * \tparam Executor1 Type of the asio executor (usually derived from the lower layer) + * \tparam Allocator Type of the allocator to be used + */ +template <class Handler, class Executor1, class Allocator> +class AsyncBase : public boost::asio::coroutine + { + public: + using allocator_type = boost::asio::associated_allocator_t<Handler, Allocator>; + using executor_type = boost::asio::associated_executor_t<Handler, Executor1>; + + allocator_type get_allocator() const noexcept + { + return boost::asio::get_associated_allocator(m_handler); + } + + executor_type get_executor() const noexcept + { + return boost::asio::get_associated_executor(m_handler, m_work_guard_1.get_executor()); + } + + protected: + template <class HandlerT> + AsyncBase(HandlerT&& handler, const Executor1& executor) + : m_handler(std::forward<HandlerT>(handler)) + , m_work_guard_1(executor) + { + } + + /** + * Call the completion handler. + * + * This function should only be called after an intermediate initiating function has been called. + * + * @param args Arguments forwarded to the completion handler function. + */ + template<class... Args> + void complete_now(Args&& ... args) + { + m_work_guard_1.reset(); + m_handler(std::forward<Args>(args)...); + } + + Handler m_handler; + boost::asio::executor_work_guard<Executor1> m_work_guard_1; + }; + +template <class Handler, class Stream, class MutableBufferSequence, class Allocator = std::allocator<void>> +class AsyncReadOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator> + { + public: + /** + * Construct and invoke an AsyncWriteOperation. + * + * @param handler Handler function to be called upon completion. + * @param stream The stream from which the data will be read + * @param buffers The buffers into which the data will be read. + * @param ec Optional error code; used to report an error to the handler function. + */ + template <class HandlerT> + AsyncReadOperation(HandlerT&& handler, + Stream& stream, + const MutableBufferSequence& buffers, + const boost::system::error_code& ec = {}) + : AsyncBase<Handler, typename Stream::executor_type, Allocator>( + std::forward<HandlerT>(handler), + stream.get_executor()) + , m_stream(stream) + , m_buffers(buffers) + , m_decodedBytes(0) + { + this->operator()(ec, std::size_t(0), false); + } + + AsyncReadOperation(AsyncReadOperation&&) = default; + + void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true) + { + reenter(this) + { + if(bytes_transferred > 0 && !ec) + { + // We have received encrypted data from the network, now hand it to TLS::Channel for decryption. + boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytes_transferred}; + try + { + m_stream.native_handle()->received_data( + static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size() + ); + } + catch(const TLS_Exception& e) + { + ec = e.type(); + } + catch(const Botan::Exception& e) + { + ec = e.error_type(); + } + catch(...) + { + ec = Botan::ErrorType::Unknown; + } + } + + if(!m_stream.hasReceivedData() && !ec && boost::asio::buffer_size(m_buffers) > 0) + { + // The channel did not decrypt a complete record yet, we need more data from the socket. + m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this)); + return; + } + + if(m_stream.hasReceivedData() && !ec) + { + // The channel has decrypted a TLS record, now copy it to the output buffers. + m_decodedBytes = m_stream.copyReceivedData(m_buffers); + } + + if(!isContinuation) + { + // Make sure the handler is not called without an intermediate initiating function. + // "Reading" into a zero-byte buffer will complete immediately. + m_ec = ec; + yield m_stream.next_layer().async_read_some(boost::asio::mutable_buffer(), std::move(*this)); + ec = m_ec; + } + + this->complete_now(ec, m_decodedBytes); + } + } + + private: + Stream& m_stream; + MutableBufferSequence m_buffers; + std::size_t m_decodedBytes; + boost::system::error_code m_ec; + }; + +template <typename Handler, class Stream, class Allocator = std::allocator<void>> +class AsyncWriteOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator> + { + public: + /** + * Construct and invoke an AsyncWriteOperation. + * + * @param handler Handler function to be called upon completion. + * @param stream The stream from which the data will be read + * @param plainBytesTransferred Number of bytes to be reported to the user-provided handler function as + * bytes_transferred. This needs to be provided since the amount of plaintext data + * consumed from the input buffer can differ from the amount of encrypted data written + * to the next layer. + * @param ec Optional error code; used to report an error to the handler function. + */ + template <class HandlerT> + AsyncWriteOperation(HandlerT&& handler, + Stream& stream, + std::size_t plainBytesTransferred, + const boost::system::error_code& ec = {}) + : AsyncBase<Handler, typename Stream::executor_type, Allocator>( + std::forward<HandlerT>(handler), + stream.get_executor()) + , m_stream(stream) + , m_plainBytesTransferred(plainBytesTransferred) + { + this->operator()(ec, std::size_t(0), false); + } + + AsyncWriteOperation(AsyncWriteOperation&&) = default; + + void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true) + { + reenter(this) + { + // mark the number of encrypted bytes sent to the network as "consumed" + // Note: bytes_transferred will be zero on first call + m_stream.consumeSendBuffer(bytes_transferred); + + if(m_stream.hasDataToSend() && !ec) + { + m_stream.next_layer().async_write_some(m_stream.sendBuffer(), std::move(*this)); + return; + } + + if(!isContinuation) + { + // Make sure the handler is not called without an intermediate initiating function. + // "Writing" to a zero-byte buffer will complete immediately. + m_ec = ec; + yield m_stream.next_layer().async_write_some(boost::asio::const_buffer(), std::move(*this)); + ec = m_ec; + } + + // The size of the sent TLS record can differ from the size of the payload due to TLS encryption. We need to + // tell the handler how many bytes of the original data we already processed. + this->complete_now(ec, m_plainBytesTransferred); + } + } + + private: + Stream& m_stream; + std::size_t m_plainBytesTransferred; + boost::system::error_code m_ec; + }; + +template <class Handler, class Stream, class Allocator = std::allocator<void>> +class AsyncHandshakeOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator> + { + public: + /** + * Construct and invoke an AsyncHandshakeOperation. + * + * @param handler Handler function to be called upon completion. + * @param stream The stream from which the data will be read + * @param ec Optional error code; used to report an error to the handler function. + */ + template<class HandlerT> + AsyncHandshakeOperation( + HandlerT&& handler, + Stream& stream, + const boost::system::error_code& ec = {}) + : AsyncBase<Handler, typename Stream::executor_type, Allocator>( + std::forward<HandlerT>(handler), + stream.get_executor()) + , m_stream(stream) + { + this->operator()(ec, std::size_t(0), false); + } + + AsyncHandshakeOperation(AsyncHandshakeOperation&&) = default; + + void operator()(boost::system::error_code ec, std::size_t bytesTransferred, bool isContinuation = true) + { + reenter(this) + { + if(bytesTransferred > 0 && !ec) + { + // Provide encrypted TLS data received from the network to TLS::Channel for decryption + boost::asio::const_buffer read_buffer {m_stream.input_buffer().data(), bytesTransferred}; + try + { + m_stream.native_handle()->received_data( + static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size() + ); + } + catch(const TLS_Exception& e) + { + ec = e.type(); + } + catch(const Botan::Exception& e) + { + ec = e.error_type(); + } + catch(...) + { + ec = Botan::ErrorType::Unknown; + } + } + + if(m_stream.hasDataToSend() && !ec) + { + // Write encrypted TLS data provided by the TLS::Channel on the wire + + // Note: we construct `AsyncWriteOperation` with 0 as its last parameter (`plainBytesTransferred`). This + // operation will eventually call `*this` as its own handler, passing the 0 back to this call operator. + // This is necessary because the check of `bytesTransferred > 0` assumes that `bytesTransferred` bytes + // were just read and are available in input_buffer for further processing. + AsyncWriteOperation< + AsyncHandshakeOperation<typename std::decay<Handler>::type, Stream, Allocator>, + Stream, + Allocator> + op{std::move(*this), m_stream, 0}; + return; + } + + if(!m_stream.native_handle()->is_active() && !ec) + { + // Read more encrypted TLS data from the network + m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this)); + return; + } + + if(!isContinuation) + { + // Make sure the handler is not called without an intermediate initiating function. + // "Reading" into a zero-byte buffer will complete immediately. + m_ec = ec; + yield m_stream.next_layer().async_read_some(boost::asio::mutable_buffer(), std::move(*this)); + ec = m_ec; + } + + this->complete_now(ec); + } + } + + private: + Stream& m_stream; + boost::system::error_code m_ec; + }; + +} // namespace detail +} // namespace TLS +} // namespace Botan + +#include <boost/asio/unyield.hpp> + +#endif // BOOST_VERSION +#endif // BOTAN_ASIO_ASYNC_OPS_H_ diff --git a/src/lib/tls/asio/asio_async_read_op.h b/src/lib/tls/asio/asio_async_read_op.h deleted file mode 100644 index 778977011..000000000 --- a/src/lib/tls/asio/asio_async_read_op.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_ASYNC_READ_OP_H_ -#define BOTAN_ASIO_ASYNC_READ_OP_H_ - -#include <botan/build.h> - -#include <boost/version.hpp> -#if BOOST_VERSION >= 106600 - -#include <botan/asio_error.h> -#include <botan/internal/asio_async_base.h> -#include <botan/internal/asio_includes.h> - -#include <boost/asio/yield.hpp> - -namespace Botan { - -namespace TLS { - -template <class Handler, class Stream, class MutableBufferSequence, class Allocator = std::allocator<void>> -class AsyncReadOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator> - { - public: - /** - * Construct and invoke an AsyncWriteOperation. - * - * @param handler Handler function to be called upon completion. - * @param stream The stream from which the data will be read - * @param buffers The buffers into which the data will be read. - * @param ec Optional error code; used to report an error to the handler function. - */ - template <class HandlerT> - AsyncReadOperation(HandlerT&& handler, - Stream& stream, - const MutableBufferSequence& buffers, - const boost::system::error_code& ec = {}) - : AsyncBase<Handler, typename Stream::executor_type, Allocator>( - std::forward<HandlerT>(handler), - stream.get_executor()) - , m_stream(stream) - , m_buffers(buffers) - , m_decodedBytes(0) - { - this->operator()(ec, std::size_t(0), false); - } - - AsyncReadOperation(AsyncReadOperation&&) = default; - - void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true) - { - reenter(this) - { - if(bytes_transferred > 0 && !ec) - { - // We have received encrypted data from the network, now hand it to TLS::Channel for decryption. - boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytes_transferred}; - try - { - m_stream.native_handle()->received_data( - static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size() - ); - } - catch(const TLS_Exception& e) - { - ec = e.type(); - } - catch(const Botan::Exception& e) - { - ec = e.error_type(); - } - catch(...) - { - ec = Botan::ErrorType::Unknown; - } - } - - if(!m_stream.hasReceivedData() && !ec && boost::asio::buffer_size(m_buffers) > 0) - { - // The channel did not decrypt a complete record yet, we need more data from the socket. - m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this)); - return; - } - - if(m_stream.hasReceivedData() && !ec) - { - // The channel has decrypted a TLS record, now copy it to the output buffers. - m_decodedBytes = m_stream.copyReceivedData(m_buffers); - } - - if(!isContinuation) - { - // Make sure the handler is not called without an intermediate initiating function. - // "Reading" into a zero-byte buffer will complete immediately. - m_ec = ec; - yield m_stream.next_layer().async_read_some(boost::asio::mutable_buffer(), std::move(*this)); - ec = m_ec; - } - - this->complete_now(ec, m_decodedBytes); - } - } - - private: - Stream& m_stream; - MutableBufferSequence m_buffers; - std::size_t m_decodedBytes; - boost::system::error_code m_ec; - }; - -} // namespace TLS - -} // namespace Botan - -#include <boost/asio/unyield.hpp> - -#endif // BOOST_VERSION -#endif // BOTAN_ASIO_ASYNC_READ_OP_H_ diff --git a/src/lib/tls/asio/asio_async_write_op.h b/src/lib/tls/asio/asio_async_write_op.h deleted file mode 100644 index b14d4ab44..000000000 --- a/src/lib/tls/asio/asio_async_write_op.h +++ /dev/null @@ -1,99 +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_ASYNC_WRITE_OP_H_ -#define BOTAN_ASIO_ASYNC_WRITE_OP_H_ - -#include <botan/build.h> - -#include <boost/version.hpp> -#if BOOST_VERSION >= 106600 - -#include <botan/internal/asio_async_base.h> -#include <botan/internal/asio_includes.h> - -#include <boost/asio/yield.hpp> - -namespace Botan { - -namespace TLS { - -template <typename Handler, class Stream, class Allocator = std::allocator<void>> -class AsyncWriteOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator> - { - public: - /** - * Construct and invoke an AsyncWriteOperation. - * - * @param handler Handler function to be called upon completion. - * @param stream The stream from which the data will be read - * @param plainBytesTransferred Number of bytes to be reported to the user-provided handler function as - * bytes_transferred. This needs to be provided since the amount of plaintext data - * consumed from the input buffer can differ from the amount of encrypted data written - * to the next layer. - * @param ec Optional error code; used to report an error to the handler function. - */ - template <class HandlerT> - AsyncWriteOperation(HandlerT&& handler, - Stream& stream, - std::size_t plainBytesTransferred, - const boost::system::error_code& ec = {}) - : AsyncBase<Handler, typename Stream::executor_type, Allocator>( - std::forward<HandlerT>(handler), - stream.get_executor()) - , m_stream(stream) - , m_plainBytesTransferred(plainBytesTransferred) - { - this->operator()(ec, std::size_t(0), false); - } - - AsyncWriteOperation(AsyncWriteOperation&&) = default; - - void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true) - { - reenter(this) - { - // mark the number of encrypted bytes sent to the network as "consumed" - // Note: bytes_transferred will be zero on first call - m_stream.consumeSendBuffer(bytes_transferred); - - if(m_stream.hasDataToSend() && !ec) - { - m_stream.next_layer().async_write_some(m_stream.sendBuffer(), std::move(*this)); - return; - } - - if(!isContinuation) - { - // Make sure the handler is not called without an intermediate initiating function. - // "Writing" to a zero-byte buffer will complete immediately. - m_ec = ec; - yield m_stream.next_layer().async_write_some(boost::asio::const_buffer(), std::move(*this)); - ec = m_ec; - } - - // The size of the sent TLS record can differ from the size of the payload due to TLS encryption. We need to - // tell the handler how many bytes of the original data we already processed. - this->complete_now(ec, m_plainBytesTransferred); - } - } - - private: - Stream& m_stream; - std::size_t m_plainBytesTransferred; - boost::system::error_code m_ec; - }; - -} // namespace TLS - -} // namespace Botan - -#include <boost/asio/unyield.hpp> - -#endif // BOOST_VERSION -#endif // BOTAN_ASIO_ASYNC_WRITE_OP_H_ diff --git a/src/lib/tls/asio/asio_error.h b/src/lib/tls/asio/asio_error.h index 4e59a6906..7356ab3d7 100644 --- a/src/lib/tls/asio/asio_error.h +++ b/src/lib/tls/asio/asio_error.h @@ -23,8 +23,7 @@ namespace Botan { namespace TLS { -namespace detail { -// TLS Alerts +//! @brief An error category for TLS alerts struct BotanAlertCategory : boost::system::error_category { const char* name() const noexcept override @@ -45,6 +44,14 @@ inline const BotanAlertCategory& botan_alert_category() noexcept return category; } +inline boost::system::error_code make_error_code(Botan::TLS::Alert::Type c) + { + return boost::system::error_code(static_cast<int>(c), Botan::TLS::botan_alert_category()); + } + +} // namespace TLS + +//! @brief An error category for errors from Botan (other than TLS alerts) struct BotanErrorCategory : boost::system::error_category { const char* name() const noexcept override @@ -64,18 +71,9 @@ inline const BotanErrorCategory& botan_category() noexcept return category; } -} // namespace detail - -inline boost::system::error_code make_error_code(Botan::TLS::Alert::Type c) - { - return boost::system::error_code(static_cast<int>(c), detail::botan_alert_category()); - } - -} // namespace TLS - inline boost::system::error_code make_error_code(Botan::ErrorType e) { - return boost::system::error_code(static_cast<int>(e), Botan::TLS::detail::botan_category()); + return boost::system::error_code(static_cast<int>(e), Botan::botan_category()); } } // namespace Botan diff --git a/src/lib/tls/asio/asio_includes.h b/src/lib/tls/asio/asio_includes.h deleted file mode 100644 index 494233d55..000000000 --- a/src/lib/tls/asio/asio_includes.h +++ /dev/null @@ -1,25 +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_INCLUDES_H_ -#define BOTAN_ASIO_INCLUDES_H_ - -#include <botan/build.h> - -#ifdef BOTAN_HAS_BOOST_ASIO - - // We need to define BOOST_ASIO_DISABLE_SERIAL_PORT before any asio imports. Otherwise asio will include <termios.h>, - // which interferes with Botan's amalgamation by defining macros like 'B0' and 'FF1'. - #define BOOST_ASIO_DISABLE_SERIAL_PORT - #include <boost/asio.hpp> - #include <boost/asio/buffer.hpp> - #include <boost/asio/coroutine.hpp> - #include <boost/asio/ip/tcp.hpp> - -#endif // BOTAN_HAS_BOOST_ASIO -#endif // BOTAN_ASIO_INCLUDES_H_ diff --git a/src/lib/tls/asio/asio_stream.h b/src/lib/tls/asio/asio_stream.h index 9b501a01a..971d80b58 100644 --- a/src/lib/tls/asio/asio_stream.h +++ b/src/lib/tls/asio/asio_stream.h @@ -1,5 +1,5 @@ /* -* TLS ASIO Stream Wrapper +* TLS ASIO Stream * (C) 2018-2019 Jack Lloyd * 2018-2019 Hannes Rantzsch, Tim Oesterreich, Rene Meusel * @@ -15,32 +15,30 @@ #include <boost/version.hpp> #if BOOST_VERSION >= 106600 -#include <botan/asio_error.h> - -#include <botan/internal/asio_async_handshake_op.h> -#include <botan/internal/asio_async_read_op.h> -#include <botan/internal/asio_async_write_op.h> -#include <botan/internal/asio_includes.h> +#include <botan/asio_async_ops.h> #include <botan/asio_context.h> +#include <botan/asio_error.h> #include <botan/tls_callbacks.h> #include <botan/tls_channel.h> #include <botan/tls_client.h> #include <botan/tls_magic.h> +// We need to define BOOST_ASIO_DISABLE_SERIAL_PORT before any asio imports. Otherwise asio will include <termios.h>, +// which interferes with Botan's amalgamation by defining macros like 'B0' and 'FF1'. +#define BOOST_ASIO_DISABLE_SERIAL_PORT +#include <boost/asio.hpp> #include <boost/beast/core/flat_buffer.hpp> #include <algorithm> #include <memory> -#include <thread> #include <type_traits> namespace Botan { - namespace TLS { /** - * boost::asio compatible SSL/TLS stream + * @brief boost::asio compatible SSL/TLS stream * * Currently only the TLS::Client specialization is implemented. * @@ -99,15 +97,13 @@ class Stream 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 m_channel.get(); } + native_handle_type native_handle() { return m_native_handle.get(); } // // -- -- configuration and callback setters // - /** - * @throws Not_Implemented - */ + //! @throws Not_Implemented template<typename VerifyCallback> void set_verify_callback(VerifyCallback callback) { @@ -127,9 +123,7 @@ class Stream ec = Botan::ErrorType::NotImplemented; } - /** - * @throws Not_Implemented - */ + //! @throws Not_Implemented void set_verify_depth(int depth) { BOTAN_UNUSED(depth); @@ -147,9 +141,7 @@ class Stream ec = Botan::ErrorType::NotImplemented; } - /** - * @throws Not_Implemented - */ + //! @throws Not_Implemented template <typename verify_mode> void set_verify_mode(verify_mode v) { @@ -176,14 +168,10 @@ class Stream const boost::asio::mutable_buffer& input_buffer() { return m_input_buffer; } boost::asio::const_buffer sendBuffer() const { return m_send_buffer.data(); } // TODO: really .data() ? - /** - * Check if decrypted data is available in the receive buffer - */ + //! @brief Check if decrypted data is available in the receive buffer bool hasReceivedData() const { return m_receive_buffer.size() > 0; } - /** - * Copy decrypted data into the user-provided buffer - */ + //! @brief Copy decrypted data into the user-provided buffer template <typename MutableBufferSequence> std::size_t copyReceivedData(MutableBufferSequence buffers) { @@ -196,14 +184,10 @@ class Stream return copiedBytes; } - /** - * Check if encrypted data is available in the send buffer - */ + //! @brief Check if encrypted data is available in the send buffer bool hasDataToSend() const { return m_send_buffer.size() > 0; } - /** - * Mark bytes in the send buffer as consumed, removing them from the buffer - */ + //! @brief Mark bytes in the send buffer as consumed, removing them from the buffer void consumeSendBuffer(std::size_t bytesConsumed) { m_send_buffer.consume(bytesConsumed); } // @@ -211,11 +195,12 @@ class Stream // /** - * Performs SSL handshaking. + * @brief 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 + * @throws boost::system::system_error if error occured, or if the chosen Connection_Side is not available */ void handshake(Connection_Side side) { @@ -225,14 +210,18 @@ class Stream } /** - * Performs SSL handshaking. + * @brief 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) { - setup_channel(side); + setup_native_handle(side, ec); + if(ec) + { return; } while(!native_handle()->is_active()) { @@ -241,14 +230,12 @@ class Stream { return; } boost::asio::const_buffer read_buffer{input_buffer().data(), m_nextLayer.read_some(input_buffer(), ec)}; - if(ec) { return; } try { - native_handle()->received_data(static_cast<const uint8_t*>(read_buffer.data()), - read_buffer.size()); + native_handle()->received_data(static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size()); } catch(const TLS_Exception& e) { @@ -271,8 +258,10 @@ class Stream } /** - * Starts an asynchronous SSL handshake. + * @brief 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) @@ -285,19 +274,20 @@ class Stream { BOOST_ASIO_HANDSHAKE_HANDLER_CHECK(HandshakeHandler, handler) type_check; - setup_channel(side); + boost::system::error_code ec; + setup_native_handle(side, ec); + // If ec is set by setup_native_handle, the AsyncHandshakeOperation created below will do nothing but call the + // handler with the error_code set appropriately - no need to early return here. boost::asio::async_completion<HandshakeHandler, void(boost::system::error_code)> init(handler); - AsyncHandshakeOperation<typename std::decay<HandshakeHandler>::type, Stream> - op{std::move(init.completion_handler), *this}; + detail::AsyncHandshakeOperation<typename std::decay<HandshakeHandler>::type, Stream> + op{std::move(init.completion_handler), *this, ec}; return init.result.get(); } - /** - * @throws Not_Implemented - */ + //! @throws Not_Implemented template <typename ConstBufferSequence, typename BufferedHandshakeHandler> BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler, void(boost::system::error_code, std::size_t)) @@ -306,7 +296,6 @@ class Stream { BOTAN_UNUSED(buffers, handler); BOOST_ASIO_HANDSHAKE_HANDLER_CHECK(BufferedHandshakeHandler, handler) type_check; - validate_connection_side(side); throw Not_Implemented("buffered async handshake is not implemented"); } @@ -315,8 +304,10 @@ class Stream // /** - * Shut down SSL on the stream. + * @brief Shut down SSL on the stream. + * * The function call will block until SSL has been shut down or an error occurs. + * * @param ec Set to indicate what error occured, if any. */ void shutdown(boost::system::error_code& ec) @@ -345,8 +336,10 @@ class Stream } /** - * Shut down SSL on the stream. + * @brief Shut down SSL on the stream. + * * The function call will block until SSL has been shut down or an error occurs. + * * @throws boost::system::system_error if error occured */ void shutdown() @@ -357,8 +350,10 @@ class Stream } /** - * Asynchronously shut down SSL on the stream. + * @brief Asynchronously shut down SSL on the stream. + * * This function call always returns immediately. + * * @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) */ @@ -377,8 +372,11 @@ class Stream // /** - * Read some data from the stream. The function call will block until one or more bytes of data has - * been read successfully, or until an error occurs. + * @brief Read some data from the stream. + * + * The function call will block until one or more bytes of data has been read successfully, or until an error + * occurs. + * * @param buffers The buffers into which the data will be read. * @param ec Set to indicate what error occured, if any. * @return The number of bytes read. Returns 0 if an error occurred. @@ -398,8 +396,10 @@ class Stream } /** - * Read some data from the stream. The function call will block until one or more bytes of data has - * been read successfully, or until an error occurs. + * @brief Read some data from the stream. + * + * The function call will block until one or more bytes of data has been read successfully, or until an error + * occurs. * * @param buffers The buffers into which the data will be read. * @return The number of bytes read. Returns 0 if an error occurred. @@ -415,8 +415,10 @@ class Stream } /** - * Write some data to the stream. The function call will block until one or more bytes of data has been written - * successfully, or until an error occurs. + * @brief Write some data to the stream. + * + * The function call will block until one or more bytes of data has been written successfully, or until an error + * occurs. * * @param buffers The data to be written. * @param ec Set to indicate what error occurred, if any. @@ -438,8 +440,10 @@ class Stream } /** - * Write some data to the stream. The function call will block until one or more bytes of data has been written - * successfully, or until an error occurs. + * @brief Write some data to the stream. + * + * The function call will block until one or more bytes of data has been written successfully, or until an error + * occurs. * * @param buffers The data to be written. * @return The number of bytes written. @@ -455,7 +459,7 @@ class Stream } /** - * Start an asynchronous write. The function call always returns immediately. + * @brief Start an asynchronous write. The function call always returns immediately. * * @param buffers The data to be written. * @param handler The handler to be called when the write operation completes. Copies will be made of the handler @@ -478,19 +482,19 @@ class Stream // we cannot be sure how many bytes were committed here so clear the send_buffer and let the // AsyncWriteOperation call the handler with the error_code set consumeSendBuffer(m_send_buffer.size()); - Botan::TLS::AsyncWriteOperation<typename std::decay<WriteHandler>::type, Stream> + detail::AsyncWriteOperation<typename std::decay<WriteHandler>::type, Stream> op{std::move(init.completion_handler), *this, std::size_t(0), ec}; return init.result.get(); } - Botan::TLS::AsyncWriteOperation<typename std::decay<WriteHandler>::type, Stream> + detail::AsyncWriteOperation<typename std::decay<WriteHandler>::type, Stream> op{std::move(init.completion_handler), *this, sent}; return init.result.get(); } /** - * Start an asynchronous read. The function call always returns immediately. + * @brief Start an asynchronous read. The function call always returns immediately. * * @param buffers The buffers into which the data will be read. Although the buffers object may be copied as * necessary, ownership of the underlying buffers is retained by the caller, which must guarantee @@ -508,14 +512,14 @@ class Stream boost::asio::async_completion<ReadHandler, void(boost::system::error_code, std::size_t)> init(handler); - AsyncReadOperation<typename std::decay<ReadHandler>::type, Stream, MutableBufferSequence> + detail::AsyncReadOperation<typename std::decay<ReadHandler>::type, Stream, MutableBufferSequence> op{std::move(init.completion_handler), *this, buffers}; return init.result.get(); } protected: /** - * Helper class that implements Botan::TLS::Callbacks + * @brief Helper class that implements Botan::TLS::Callbacks * * This class is provided to the stream's native_handle (Botan::TLS::Channel) and implements the callback * functions triggered by the native_handle. @@ -568,43 +572,40 @@ class Stream boost::beast::flat_buffer& m_send_buffer; }; - // TODO: explain, note: c++17 makes this much better with constexpr if + // This is a helper construct to allow mocking the native_handle in test code. It is activated by explicitly + // specifying a (mocked) channel type template parameter when constructing the stream and does not attempt to + // instantiate the native_handle. + // Note: once we have C++17 we can achieve this much more elegantly using constexpr if. template<class T = ChannelT> typename std::enable_if<!std::is_same<Channel, T>::value>::type - setup_channel(Connection_Side) {} + setup_native_handle(Connection_Side, boost::system::error_code&) {} + /** + * @brief Create the native handle. + * + * Depending on the desired connection side, this function will create a Botan::TLS::Client or a + * Botan::TLS::Server. + * + * @param side The desired connection side (client or server) + * @param ec Set to NotImplemented when side is SERVER - currently only CLIENT is implemented + */ template<class T = ChannelT> typename std::enable_if<std::is_same<Channel, T>::value>::type - setup_channel(Connection_Side side) + setup_native_handle(Connection_Side side, boost::system::error_code& ec) { - 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) + if(side == CLIENT) { - throw Invalid_Argument("wrong connection_side"); + m_native_handle = 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) - bool validate_connection_side(Connection_Side side, boost::system::error_code& ec) - { - if(side != CLIENT) + else { - ec = Botan::ErrorType::InvalidArgument; - return false; + ec = Botan::ErrorType::NotImplemented; } - - return true; } size_t sendPendingEncryptedData(boost::system::error_code& ec) @@ -693,7 +694,7 @@ class Stream boost::beast::flat_buffer m_send_buffer; StreamCore m_core; - std::unique_ptr<ChannelT> m_channel; + std::unique_ptr<ChannelT> m_native_handle; // Buffer space used to read input intended for the core std::vector<uint8_t> m_input_buffer_space; @@ -701,7 +702,6 @@ class Stream }; } // namespace TLS - } // namespace Botan #endif // BOOST_VERSION diff --git a/src/lib/tls/asio/info.txt b/src/lib/tls/asio/info.txt index 1e37ea2a3..7862a1c28 100644 --- a/src/lib/tls/asio/info.txt +++ b/src/lib/tls/asio/info.txt @@ -6,16 +6,9 @@ TLS_ASIO_STREAM -> 20181218 asio_context.h asio_stream.h asio_error.h +asio_async_ops.h </header:public> -<header:internal> -asio_async_base.h -asio_async_handshake_op.h -asio_async_read_op.h -asio_async_write_op.h -asio_includes.h -</header:internal> - <requires> boost tls diff --git a/src/tests/unit_asio_stream.cpp b/src/tests/unit_asio_stream.cpp index a2d423785..704c4f305 100644 --- a/src/tests/unit_asio_stream.cpp +++ b/src/tests/unit_asio_stream.cpp @@ -1,5 +1,5 @@ /* -* TLS ASIO Stream Wrapper +* TLS ASIO Stream Unit Tests * (C) 2018-2019 Jack Lloyd * 2018-2019 Hannes Rantzsch, Tim Oesterreich, Rene Meusel * @@ -101,7 +101,7 @@ class AsioStream : public Botan::TLS::Stream<TestStream, MockChannel> AsioStream(Botan::TLS::Context& context, Args&& ... args) : Stream(context, args...) { - m_channel = std::unique_ptr<MockChannel>(new MockChannel(m_core)); + m_native_handle = std::unique_ptr<MockChannel>(new MockChannel(m_core)); } virtual ~AsioStream() = default; @@ -114,7 +114,7 @@ class ThrowingAsioStream : public Botan::TLS::Stream<TestStream, ThrowingMockCha ThrowingAsioStream(Botan::TLS::Context& context, Args&& ... args) : Stream(context, args...) { - m_channel = std::unique_ptr<ThrowingMockChannel>(new ThrowingMockChannel(m_core)); + m_native_handle = std::unique_ptr<ThrowingMockChannel>(new ThrowingMockChannel(m_core)); } virtual ~ThrowingAsioStream() = default; |