summaryrefslogtreecommitdiffstats
path: root/comm/third_party/botan/src/lib/tls/asio
diff options
context:
space:
mode:
Diffstat (limited to 'comm/third_party/botan/src/lib/tls/asio')
-rw-r--r--comm/third_party/botan/src/lib/tls/asio/asio_async_ops.h355
-rw-r--r--comm/third_party/botan/src/lib/tls/asio/asio_context.h120
-rw-r--r--comm/third_party/botan/src/lib/tls/asio/asio_error.h151
-rw-r--r--comm/third_party/botan/src/lib/tls/asio/asio_stream.h835
-rw-r--r--comm/third_party/botan/src/lib/tls/asio/info.txt15
5 files changed, 1476 insertions, 0 deletions
diff --git a/comm/third_party/botan/src/lib/tls/asio/asio_async_ops.h b/comm/third_party/botan/src/lib/tls/asio/asio_async_ops.h
new file mode 100644
index 0000000000..47267a569d
--- /dev/null
+++ b/comm/third_party/botan/src/lib/tls/asio/asio_async_ops.h
@@ -0,0 +1,355 @@
+/*
+* Helpers for TLS ASIO Stream
+* (C) 2018-2020 Jack Lloyd
+* 2018-2020 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 AsyncReadOperation.
+ *
+ * @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};
+ m_stream.process_encrypted_data(read_buffer, ec);
+ }
+
+ if (m_stream.shutdown_received())
+ {
+ // we just received a 'close_notify' from the peer and don't expect any more data
+ ec = boost::asio::error::eof;
+ }
+ else if (ec == boost::asio::error::eof)
+ {
+ // we did not expect this disconnection from the peer
+ ec = StreamError::StreamTruncated;
+ }
+
+ if(!m_stream.has_received_data() && !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.has_received_data() && !ec)
+ {
+ // The channel has decrypted a TLS record, now copy it to the output buffers.
+ m_decodedBytes = m_stream.copy_received_data(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.consume_send_buffer(bytes_transferred);
+
+ if(m_stream.has_data_to_send() && !ec)
+ {
+ m_stream.next_layer().async_write_some(m_stream.send_buffer(), std::move(*this));
+ return;
+ }
+
+ if (ec == boost::asio::error::eof && !m_stream.shutdown_received())
+ {
+ // transport layer was closed by peer without receiving 'close_notify'
+ ec = StreamError::StreamTruncated;
+ }
+
+ 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(ec == boost::asio::error::eof)
+ {
+ ec = StreamError::StreamTruncated;
+ }
+
+ 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};
+ m_stream.process_encrypted_data(read_buffer, ec);
+ }
+
+ if(m_stream.has_data_to_send() && !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/comm/third_party/botan/src/lib/tls/asio/asio_context.h b/comm/third_party/botan/src/lib/tls/asio/asio_context.h
new file mode 100644
index 0000000000..e225fde6a2
--- /dev/null
+++ b/comm/third_party/botan/src/lib/tls/asio/asio_context.h
@@ -0,0 +1,120 @@
+/*
+ * TLS Context
+ * (C) 2018-2020 Jack Lloyd
+ * 2018-2020 Hannes Rantzsch, Tim Oesterreich, Rene Meusel
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ */
+
+#ifndef BOTAN_ASIO_TLS_CONTEXT_H_
+#define BOTAN_ASIO_TLS_CONTEXT_H_
+
+#include <botan/build.h>
+
+#include <boost/version.hpp>
+#if BOOST_VERSION >= 106600
+
+#include <functional>
+
+#include <botan/credentials_manager.h>
+#include <botan/ocsp.h>
+#include <botan/rng.h>
+#include <botan/tls_callbacks.h>
+#include <botan/tls_policy.h>
+#include <botan/tls_server_info.h>
+#include <botan/tls_session_manager.h>
+
+namespace Botan {
+namespace TLS {
+
+namespace detail {
+template <typename FunT>
+struct fn_signature_helper : public std::false_type {};
+
+template <typename R, typename D, typename... Args>
+struct fn_signature_helper<R(D::*)(Args...)>
+ {
+ using type = std::function<R(Args...)>;
+ };
+} // namespace detail
+
+/**
+ * A helper class to initialize and configure Botan::TLS::Stream
+ */
+class Context
+ {
+ public:
+ // statically extract the function signature type from Callbacks::tls_verify_cert_chain
+ // and reuse it as an std::function<> for the verify callback signature
+ /**
+ * The signature of the callback function should correspond to the signature of
+ * Callbacks::tls_verify_cert_chain
+ */
+ using Verify_Callback =
+ detail::fn_signature_helper<decltype(&Callbacks::tls_verify_cert_chain)>::type;
+
+ Context(Credentials_Manager& credentials_manager,
+ RandomNumberGenerator& rng,
+ Session_Manager& session_manager,
+ Policy& policy,
+ Server_Information server_info = Server_Information()) :
+ m_credentials_manager(credentials_manager),
+ m_rng(rng),
+ m_session_manager(session_manager),
+ m_policy(policy),
+ m_server_info(server_info)
+ {}
+
+ virtual ~Context() = default;
+
+ Context(Context&&) = default;
+ Context(const Context&) = delete;
+ Context& operator=(const Context&) = delete;
+ Context& operator=(Context&&) = delete;
+
+ /**
+ * @brief Override the tls_verify_cert_chain callback
+ *
+ * This changes the verify_callback in the stream's TLS::Context, and hence the tls_verify_cert_chain callback
+ * used in the handshake.
+ * Using this function is equivalent to setting the callback via @see Botan::TLS::Stream::set_verify_callback
+ *
+ * @note This function should only be called before initiating the TLS handshake
+ */
+ void set_verify_callback(Verify_Callback callback)
+ {
+ m_verify_callback = std::move(callback);
+ }
+
+ bool has_verify_callback() const
+ {
+ return static_cast<bool>(m_verify_callback);
+ }
+
+ const Verify_Callback& get_verify_callback() const
+ {
+ return m_verify_callback;
+ }
+
+ void set_server_info(const Server_Information& server_info)
+ {
+ m_server_info = server_info;
+ }
+
+ protected:
+ template <class S, class C> friend class Stream;
+
+ Credentials_Manager& m_credentials_manager;
+ RandomNumberGenerator& m_rng;
+ Session_Manager& m_session_manager;
+ Policy& m_policy;
+
+ Server_Information m_server_info;
+ Verify_Callback m_verify_callback;
+ };
+
+} // namespace TLS
+} // namespace Botan
+
+#endif // BOOST_VERSION
+#endif // BOTAN_ASIO_TLS_CONTEXT_H_
diff --git a/comm/third_party/botan/src/lib/tls/asio/asio_error.h b/comm/third_party/botan/src/lib/tls/asio/asio_error.h
new file mode 100644
index 0000000000..bdaf8473f9
--- /dev/null
+++ b/comm/third_party/botan/src/lib/tls/asio/asio_error.h
@@ -0,0 +1,151 @@
+/*
+* TLS Stream Errors
+* (C) 2018-2020 Jack Lloyd
+* 2018-2020 Hannes Rantzsch, Tim Oesterreich, Rene Meusel
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_ASIO_ERROR_H_
+#define BOTAN_ASIO_ERROR_H_
+
+#include <botan/build.h>
+
+#include <boost/version.hpp>
+#if BOOST_VERSION >= 106600
+
+#include <boost/system/system_error.hpp>
+
+#include <botan/exceptn.h>
+#include <botan/tls_alert.h>
+#include <botan/tls_exceptn.h>
+
+/*
+ * This file defines Botan-specific subclasses of boost::system::error_category.
+ * In addition to the class definition, each category class is accompanied by function `make_error_code` used to create
+ * a `boost::system::error_code` of the category from some other kind of error in Botan (for example, a TLS alert).
+ * Since error_category instances should be singletons, there's also a method to get/create the instance for each class.
+ */
+
+namespace Botan {
+namespace TLS {
+
+enum StreamError
+ {
+ StreamTruncated = 1
+ };
+
+//! @brief An error category for errors from the TLS::Stream
+struct StreamCategory : public boost::system::error_category
+ {
+ public:
+ const char* name() const noexcept override
+ {
+ return "Botan TLS Stream";
+ }
+
+ std::string message(int value) const override
+ {
+ switch(value)
+ {
+ case StreamTruncated:
+ return "stream truncated";
+ default:
+ return "generic error";
+ }
+ }
+ };
+
+inline const StreamCategory& botan_stream_category()
+ {
+ static StreamCategory category;
+ return category;
+ }
+
+inline boost::system::error_code make_error_code(Botan::TLS::StreamError e)
+ {
+ return boost::system::error_code(static_cast<int>(e), Botan::TLS::botan_stream_category());
+ }
+
+//! @brief An error category for TLS alerts
+struct BotanAlertCategory : boost::system::error_category
+ {
+ const char* name() const noexcept override
+ {
+ return "Botan TLS Alert";
+ }
+
+ std::string message(int ev) const override
+ {
+ Botan::TLS::Alert alert(static_cast<Botan::TLS::Alert::Type>(ev));
+ return alert.type_string();
+ }
+ };
+
+inline const BotanAlertCategory& botan_alert_category() noexcept
+ {
+ static BotanAlertCategory category;
+ 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
+ {
+ return "Botan";
+ }
+
+ std::string message(int ev) const override
+ {
+ return Botan::to_string(static_cast<Botan::ErrorType>(ev));
+ }
+ };
+
+inline const BotanErrorCategory& botan_category() noexcept
+ {
+ static BotanErrorCategory category;
+ return category;
+ }
+
+inline boost::system::error_code make_error_code(Botan::ErrorType e)
+ {
+ return boost::system::error_code(static_cast<int>(e), Botan::botan_category());
+ }
+
+} // namespace Botan
+
+ /*
+ * Add a template specialization of `is_error_code_enum` for each kind of error to allow automatic conversion to an
+ * error code.
+ */
+namespace boost {
+namespace system {
+
+template<> struct is_error_code_enum<Botan::TLS::Alert::Type>
+ {
+ static const bool value = true;
+ };
+
+template<> struct is_error_code_enum<Botan::TLS::StreamError>
+ {
+ static const bool value = true;
+ };
+
+template<> struct is_error_code_enum<Botan::ErrorType>
+ {
+ static const bool value = true;
+ };
+
+} // namespace system
+} // namespace boost
+
+#endif // BOOST_VERSION
+#endif // BOTAN_ASIO_ERROR_H_
diff --git a/comm/third_party/botan/src/lib/tls/asio/asio_stream.h b/comm/third_party/botan/src/lib/tls/asio/asio_stream.h
new file mode 100644
index 0000000000..bef95e44a0
--- /dev/null
+++ b/comm/third_party/botan/src/lib/tls/asio/asio_stream.h
@@ -0,0 +1,835 @@
+/*
+* TLS ASIO Stream
+* (C) 2018-2020 Jack Lloyd
+* 2018-2020 Hannes Rantzsch, Tim Oesterreich, Rene Meusel
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_ASIO_STREAM_H_
+#define BOTAN_ASIO_STREAM_H_
+
+#include <botan/build.h>
+
+// first version to be compatible with Networking TS (N4656) and boost::beast
+#include <boost/version.hpp>
+#if BOOST_VERSION >= 106600
+
+#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>
+#include <botan/tls_server.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.hpp>
+
+#include <algorithm>
+#include <memory>
+#include <type_traits>
+
+namespace Botan {
+namespace TLS {
+
+/**
+ * @brief boost::asio compatible SSL/TLS stream
+ *
+ * @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 ChannelT = Channel>
+class Stream
+ {
+ public:
+ //! \name construction
+ //! @{
+
+ /**
+ * @brief Construct a new Stream
+ *
+ * @param context The context parameter is used to set up the underlying native handle. Using code is
+ * responsible for lifetime management of the context and must ensure that it is available for the
+ * lifetime of the stream.
+ * @param args Arguments to be forwarded to the construction of the next layer.
+ */
+ template <typename... Args>
+ explicit Stream(Context& context, Args&& ... args)
+ : m_context(context)
+ , m_nextLayer(std::forward<Args>(args)...)
+ , m_core(*this)
+ , m_shutdown_received(false)
+ , m_input_buffer_space(MAX_CIPHERTEXT_SIZE, '\0')
+ , m_input_buffer(m_input_buffer_space.data(), m_input_buffer_space.size())
+ {}
+
+ /**
+ * @brief Construct a new Stream
+ *
+ * Convenience overload for boost::asio::ssl::stream compatibility.
+ *
+ * @param arg This argument is forwarded to the construction of the next layer.
+ * @param context The context parameter is used to set up the underlying native handle. Using code is
+ * responsible for lifetime management of the context and must ensure that is available for the
+ * lifetime of the stream.
+ */
+ template <typename Arg>
+ explicit Stream(Arg&& arg, Context& context)
+ : m_context(context)
+ , m_nextLayer(std::forward<Arg>(arg))
+ , m_core(*this)
+ , m_shutdown_received(false)
+ , m_input_buffer_space(MAX_CIPHERTEXT_SIZE, '\0')
+ , m_input_buffer(m_input_buffer_space.data(), m_input_buffer_space.size())
+ {}
+
+ virtual ~Stream() = default;
+
+ Stream(Stream&& other) = default;
+ Stream& operator=(Stream&& other) = default;
+
+ Stream(const Stream& other) = delete;
+ Stream& operator=(const Stream& other) = delete;
+
+ //! @}
+ //! \name boost::asio accessor methods
+ //! @{
+
+ using next_layer_type = typename std::remove_reference<StreamLayer>::type;
+
+ const next_layer_type& next_layer() const { return m_nextLayer; }
+ next_layer_type& next_layer() { return m_nextLayer; }
+
+#if BOOST_VERSION >= 107000
+ /*
+ * From Boost 1.70 onwards Beast types no longer provide public access to the member function `lowest_layer()`.
+ * Instead, the new free-standing functions in Beast need to be used.
+ * See also: https://github.com/boostorg/beast/commit/6a658b5c3a36f8d58334f8b6582c01c3e87768ae
+ */
+ using lowest_layer_type = typename boost::beast::lowest_layer_type<StreamLayer>;
+
+ lowest_layer_type& lowest_layer() { return boost::beast::get_lowest_layer(m_nextLayer); }
+ const lowest_layer_type& lowest_layer() const { return boost::beast::get_lowest_layer(m_nextLayer); }
+#else
+ using lowest_layer_type = typename next_layer_type::lowest_layer_type;
+
+ lowest_layer_type& lowest_layer() { return m_nextLayer.lowest_layer(); }
+ const lowest_layer_type& lowest_layer() const { return m_nextLayer.lowest_layer(); }
+#endif
+
+ using executor_type = typename next_layer_type::executor_type;
+ executor_type get_executor() noexcept { return m_nextLayer.get_executor(); }
+
+ using native_handle_type = typename std::add_pointer<ChannelT>::type;
+ native_handle_type native_handle()
+ {
+ if(m_native_handle == nullptr)
+ { throw Invalid_State("Invalid handshake state"); }
+ return m_native_handle.get();
+ }
+
+ //! @}
+ //! \name configuration and callback setters
+ //! @{
+
+ /**
+ * @brief Override the tls_verify_cert_chain callback
+ *
+ * This changes the verify_callback in the stream's TLS::Context, and hence the tls_verify_cert_chain callback
+ * used in the handshake.
+ * Using this function is equivalent to setting the callback via @see Botan::TLS::Context::set_verify_callback
+ *
+ * @note This function should only be called before initiating the TLS handshake
+ */
+ void set_verify_callback(Context::Verify_Callback callback)
+ {
+ m_context.set_verify_callback(std::move(callback));
+ }
+
+ /**
+ * @brief Compatibility overload of @ref set_verify_callback
+ *
+ * @param callback the callback implementation
+ * @param ec This parameter is unused.
+ */
+ void set_verify_callback(Context::Verify_Callback callback, boost::system::error_code& ec)
+ {
+ BOTAN_UNUSED(ec);
+ m_context.set_verify_callback(std::move(callback));
+ }
+
+ //! @throws Not_Implemented
+ void set_verify_depth(int depth)
+ {
+ BOTAN_UNUSED(depth);
+ throw Not_Implemented("set_verify_depth is not implemented");
+ }
+
+ /**
+ * Not Implemented.
+ * @param depth the desired verification depth
+ * @param ec Will be set to `Botan::ErrorType::NotImplemented`
+ */
+ void set_verify_depth(int depth, boost::system::error_code& ec)
+ {
+ BOTAN_UNUSED(depth);
+ ec = Botan::ErrorType::NotImplemented;
+ }
+
+ //! @throws Not_Implemented
+ template <typename verify_mode>
+ void set_verify_mode(verify_mode v)
+ {
+ BOTAN_UNUSED(v);
+ throw Not_Implemented("set_verify_mode is not implemented");
+ }
+
+ /**
+ * Not Implemented.
+ * @param v the desired verify mode
+ * @param ec Will be set to `Botan::ErrorType::NotImplemented`
+ */
+ template <typename verify_mode>
+ void set_verify_mode(verify_mode v, boost::system::error_code& ec)
+ {
+ BOTAN_UNUSED(v);
+ ec = Botan::ErrorType::NotImplemented;
+ }
+
+ //! @}
+ //! \name handshake methods
+ //! @{
+
+ /**
+ * @brief Performs SSL handshaking.
+ *
+ * The function call will block until handshaking is complete or an error occurs.
+ *
+ * @param side The type of handshaking to be performed, i.e. as a client or as a server.
+ * @throws boost::system::system_error if error occured
+ */
+ void handshake(Connection_Side side)
+ {
+ boost::system::error_code ec;
+ handshake(side, ec);
+ boost::asio::detail::throw_error(ec, "handshake");
+ }
+
+ /**
+ * @brief Performs SSL handshaking.
+ *
+ * The function call will block until handshaking is complete or an error occurs.
+ *
+ * @param side 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_native_handle(side, ec);
+
+ if(side == CLIENT)
+ {
+ // send client hello, which was written to the send buffer on client instantiation
+ send_pending_encrypted_data(ec);
+ }
+
+ while(!native_handle()->is_active() && !ec)
+ {
+ boost::asio::const_buffer read_buffer{input_buffer().data(), m_nextLayer.read_some(input_buffer(), ec)};
+ if(ec)
+ { return; }
+
+ process_encrypted_data(read_buffer, ec);
+
+ send_pending_encrypted_data(ec);
+ }
+ }
+
+ /**
+ * @brief Starts an asynchronous SSL handshake.
+ *
+ * This function call always returns immediately.
+ *
+ * @param side 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)
+ */
+ template <typename HandshakeHandler>
+ auto async_handshake(Connection_Side side, HandshakeHandler&& handler) ->
+ BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, void(boost::system::error_code))
+ {
+ BOOST_ASIO_HANDSHAKE_HANDLER_CHECK(HandshakeHandler, handler) type_check;
+
+ 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);
+
+ detail::AsyncHandshakeOperation<typename std::decay<HandshakeHandler>::type, Stream>
+ op{std::move(init.completion_handler), *this, ec};
+
+ return init.result.get();
+ }
+
+ //! @throws Not_Implemented
+ template <typename ConstBufferSequence, typename BufferedHandshakeHandler>
+ BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler,
+ void(boost::system::error_code, std::size_t))
+ async_handshake(Connection_Side side, const ConstBufferSequence& buffers,
+ BufferedHandshakeHandler&& handler)
+ {
+ BOTAN_UNUSED(side, buffers, handler);
+ BOOST_ASIO_HANDSHAKE_HANDLER_CHECK(BufferedHandshakeHandler, handler) type_check;
+ throw Not_Implemented("buffered async handshake is not implemented");
+ }
+
+ //! @}
+ //! \name shutdown methods
+ //! @{
+
+ /**
+ * @brief Shut down SSL on the stream.
+ *
+ * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down
+ * or an error occurs. Note that this will not close the lowest layer.
+ *
+ * Note that this can be used in reaction of a received shutdown alert from the peer.
+ *
+ * @param ec Set to indicate what error occured, if any.
+ */
+ void shutdown(boost::system::error_code& ec)
+ {
+ try_with_error_code([&]
+ {
+ native_handle()->close();
+ }, ec);
+
+ send_pending_encrypted_data(ec);
+ }
+
+ /**
+ * @brief Shut down SSL on the stream.
+ *
+ * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down
+ * or an error occurs. Note that this will not close the lowest layer.
+ *
+ * Note that this can be used in reaction of a received shutdown alert from the peer.
+ *
+ * @throws boost::system::system_error if error occured
+ */
+ void shutdown()
+ {
+ boost::system::error_code ec;
+ shutdown(ec);
+ boost::asio::detail::throw_error(ec, "shutdown");
+ }
+
+ private:
+ /**
+ * @brief Internal wrapper type to adapt the expected signature of `async_shutdown` to the completion handler
+ * signature of `AsyncWriteOperation`.
+ *
+ * This is boilerplate to ignore the `size_t` parameter that is passed to the completion handler of
+ * `AsyncWriteOperation`. Note that it needs to retain the wrapped handler's executor.
+ */
+ template <typename Handler, typename Executor>
+ struct Wrapper
+ {
+ void operator()(boost::system::error_code ec, std::size_t)
+ {
+ handler(ec);
+ }
+
+ using executor_type = boost::asio::associated_executor_t<Handler, Executor>;
+
+ executor_type get_executor() const noexcept
+ {
+ return boost::asio::get_associated_executor(handler, io_executor);
+ }
+
+ using allocator_type = boost::asio::associated_allocator_t<Handler>;
+
+ allocator_type get_allocator() const noexcept
+ {
+ return boost::asio::get_associated_allocator(handler);
+ }
+
+ Handler handler;
+ Executor io_executor;
+ };
+
+ public:
+ /**
+ * @brief Asynchronously shut down SSL on the stream.
+ *
+ * This function call always returns immediately.
+ *
+ * Note that this can be used in reaction of a received shutdown alert from the peer.
+ *
+ * @param handler The handler to be called when the shutdown operation completes.
+ * The equivalent function signature of the handler must be: void(boost::system::error_code)
+ */
+ template <typename ShutdownHandler>
+ void async_shutdown(ShutdownHandler&& handler)
+ {
+ boost::system::error_code ec;
+ try_with_error_code([&]
+ {
+ native_handle()->close();
+ }, ec);
+ // If ec is set by native_handle->close(), the AsyncWriteOperation created below will do nothing but call the
+ // handler with the error_code set appropriately - no need to early return here.
+
+ using ShutdownHandlerWrapper = Wrapper<ShutdownHandler, typename Stream::executor_type>;
+
+ ShutdownHandlerWrapper w{std::forward<ShutdownHandler>(handler), get_executor()};
+ BOOST_ASIO_SHUTDOWN_HANDLER_CHECK(ShutdownHandler, w) type_check;
+
+ boost::asio::async_completion<ShutdownHandlerWrapper, void(boost::system::error_code, std::size_t)>
+ init(w);
+
+ detail::AsyncWriteOperation<typename std::decay<ShutdownHandlerWrapper>::type, Stream>
+ op{std::move(init.completion_handler), *this, boost::asio::buffer_size(send_buffer())};
+
+ return init.result.get();
+ }
+
+ //! @}
+ //! \name I/O methods
+ //! @{
+
+ /**
+ * @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 occurred, if any. Specifically, StreamTruncated will be set if the peer
+ * has closed the connection but did not properly shut down the SSL connection.
+ * @return The number of bytes read. Returns 0 if an error occurred.
+ */
+ template <typename MutableBufferSequence>
+ std::size_t read_some(const MutableBufferSequence& buffers,
+ boost::system::error_code& ec)
+ {
+ if(has_received_data())
+ { return copy_received_data(buffers); }
+
+ boost::asio::const_buffer read_buffer{input_buffer().data(), m_nextLayer.read_some(input_buffer(), ec)};
+ if(ec)
+ { return 0; }
+
+ process_encrypted_data(read_buffer, ec);
+
+ if(ec) // something went wrong in process_encrypted_data()
+ { return 0; }
+
+ if(shutdown_received())
+ {
+ // we just received a 'close_notify' from the peer and don't expect any more data
+ ec = boost::asio::error::eof;
+ }
+ else if(ec == boost::asio::error::eof)
+ {
+ // we did not expect this disconnection from the peer
+ ec = StreamError::StreamTruncated;
+ }
+
+ return !ec ? copy_received_data(buffers) : 0;
+ }
+
+ /**
+ * @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.
+ * @throws boost::system::system_error if error occured
+ */
+ template <typename MutableBufferSequence>
+ std::size_t read_some(const MutableBufferSequence& buffers)
+ {
+ boost::system::error_code ec;
+ auto const n = read_some(buffers, ec);
+ boost::asio::detail::throw_error(ec, "read_some");
+ return n;
+ }
+
+ /**
+ * @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.
+ * @return The number of bytes processed from the input buffers.
+ */
+ template <typename ConstBufferSequence>
+ std::size_t write_some(const ConstBufferSequence& buffers,
+ boost::system::error_code& ec)
+ {
+ tls_encrypt(buffers, ec);
+ send_pending_encrypted_data(ec);
+ return !ec ? boost::asio::buffer_size(buffers) : 0;
+ }
+
+ /**
+ * @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.
+ * @throws boost::system::system_error if error occured
+ */
+ template <typename ConstBufferSequence>
+ std::size_t write_some(const ConstBufferSequence& buffers)
+ {
+ boost::system::error_code ec;
+ auto const n = write_some(buffers, ec);
+ boost::asio::detail::throw_error(ec, "write_some");
+ return n;
+ }
+
+ /**
+ * @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
+ * as required. The equivalent function signature of the handler must be:
+ * void(boost::system::error_code, std::size_t)
+ */
+ template <typename ConstBufferSequence, typename WriteHandler>
+ auto async_write_some(const ConstBufferSequence& buffers, WriteHandler&& handler) ->
+ BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler,
+ void(boost::system::error_code, std::size_t))
+ {
+ BOOST_ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check;
+
+ boost::asio::async_completion<WriteHandler, void(boost::system::error_code, std::size_t)> init(handler);
+
+ boost::system::error_code ec;
+ tls_encrypt(buffers, ec);
+ if(ec)
+ {
+ // 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
+ consume_send_buffer(m_send_buffer.size());
+ detail::AsyncWriteOperation<typename std::decay<WriteHandler>::type, Stream>
+ op{std::move(init.completion_handler), *this, std::size_t(0), ec};
+ return init.result.get();
+ }
+
+ detail::AsyncWriteOperation<typename std::decay<WriteHandler>::type, Stream>
+ op{std::move(init.completion_handler), *this, boost::asio::buffer_size(buffers)};
+
+ return init.result.get();
+ }
+
+ /**
+ * @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
+ * that they remain valid until the handler is called.
+ * @param handler The handler to be called when the read operation completes. The equivalent function signature of
+ * the handler must be:
+ * void(boost::system::error_code, std::size_t)
+ */
+ template <typename MutableBufferSequence, typename ReadHandler>
+ auto async_read_some(const MutableBufferSequence& buffers, ReadHandler&& handler) ->
+ BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler,
+ void(boost::system::error_code, std::size_t))
+ {
+ BOOST_ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check;
+
+ boost::asio::async_completion<ReadHandler, void(boost::system::error_code, std::size_t)> init(handler);
+
+ detail::AsyncReadOperation<typename std::decay<ReadHandler>::type, Stream, MutableBufferSequence>
+ op{std::move(init.completion_handler), *this, buffers};
+ return init.result.get();
+ }
+
+ //! @}
+
+ //! @brief Indicates whether a close_notify alert has been received from the peer.
+ bool shutdown_received() const
+ {
+ return m_shutdown_received;
+ }
+
+ protected:
+ template <class H, class S, class M, class A> friend class detail::AsyncReadOperation;
+ template <class H, class S, class A> friend class detail::AsyncWriteOperation;
+ template <class H, class S, class A> friend class detail::AsyncHandshakeOperation;
+
+ /**
+ * @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.
+ *
+ * @param receive_buffer reference to the buffer where decrypted data should be placed
+ * @param send_buffer reference to the buffer where encrypted data should be placed
+ */
+ class StreamCore : public Botan::TLS::Callbacks
+ {
+ public:
+ StreamCore(Stream& stream) : m_stream(stream) {}
+
+ virtual ~StreamCore() = default;
+
+ void tls_emit_data(const uint8_t data[], std::size_t size) override
+ {
+ m_stream.m_send_buffer.commit(
+ boost::asio::buffer_copy(m_stream.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
+ {
+ m_stream.m_receive_buffer.commit(
+ boost::asio::buffer_copy(m_stream.m_receive_buffer.prepare(size), boost::asio::const_buffer(data, size))
+ );
+ }
+
+ void tls_alert(Botan::TLS::Alert alert) override
+ {
+ if(alert.type() == Botan::TLS::Alert::CLOSE_NOTIFY)
+ {
+ m_stream.set_shutdown_received();
+ // Channel::process_alert will automatically write the corresponding close_notify response to the
+ // send_buffer and close the native_handle after this function returns.
+ }
+ }
+
+ 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;
+ }
+
+ void tls_verify_cert_chain(
+ const std::vector<X509_Certificate>& cert_chain,
+ const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_responses,
+ const std::vector<Certificate_Store*>& trusted_roots,
+ Usage_Type usage,
+ const std::string& hostname,
+ const TLS::Policy& policy) override
+ {
+ if(m_stream.m_context.has_verify_callback())
+ {
+ m_stream.m_context.get_verify_callback()(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy);
+ }
+ else
+ {
+ Callbacks::tls_verify_cert_chain(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy);
+ }
+ }
+
+ private:
+ Stream& m_stream;
+ };
+
+ const boost::asio::mutable_buffer& input_buffer() { return m_input_buffer; }
+ boost::asio::const_buffer send_buffer() const { return m_send_buffer.data(); }
+
+ //! @brief Check if decrypted data is available in the receive buffer
+ bool has_received_data() const { return m_receive_buffer.size() > 0; }
+
+ //! @brief Copy decrypted data into the user-provided buffer
+ template <typename MutableBufferSequence>
+ std::size_t copy_received_data(MutableBufferSequence buffers)
+ {
+ // Note: It would be nice to avoid this buffer copy. This could be achieved by equipping the StreamCore with
+ // the user's desired target buffer once a read is started, and reading directly into that buffer in tls_record
+ // received. However, we need to deal with the case that the receive buffer provided by the caller is smaller
+ // than the decrypted record, so this optimization might not be worth the additional complexity.
+ const auto copiedBytes = boost::asio::buffer_copy(buffers, m_receive_buffer.data());
+ m_receive_buffer.consume(copiedBytes);
+ return copiedBytes;
+ }
+
+ //! @brief Check if encrypted data is available in the send buffer
+ bool has_data_to_send() const { return m_send_buffer.size() > 0; }
+
+ //! @brief Mark bytes in the send buffer as consumed, removing them from the buffer
+ void consume_send_buffer(std::size_t bytesConsumed) { m_send_buffer.consume(bytesConsumed); }
+
+ // 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_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 indicate what error occurred, if any.
+ */
+ template<class T = ChannelT>
+ typename std::enable_if<std::is_same<Channel, T>::value>::type
+ setup_native_handle(Connection_Side side, boost::system::error_code& ec)
+ {
+ try_with_error_code([&]
+ {
+ if(side == CLIENT)
+ {
+ m_native_handle = std::unique_ptr<Client>(
+ new Client(m_core,
+ m_context.m_session_manager,
+ m_context.m_credentials_manager,
+ m_context.m_policy,
+ m_context.m_rng,
+ m_context.m_server_info,
+ Protocol_Version::latest_tls_version()));
+ }
+ else
+ {
+ m_native_handle = std::unique_ptr<Server>(
+ new Server(m_core,
+ m_context.m_session_manager,
+ m_context.m_credentials_manager,
+ m_context.m_policy,
+ m_context.m_rng,
+ false /* no DTLS */));
+ }
+ }, ec);
+ }
+
+ /** @brief Synchronously write encrypted data from the send buffer to the next layer.
+ *
+ * If this function is called with an error code other than 'Success', it will do nothing and return 0.
+ *
+ * @param ec Set to indicate what error occurred, if any. Specifically, StreamTruncated will be set if the peer
+ * has closed the connection but did not properly shut down the SSL connection.
+ * @return The number of bytes written.
+ */
+ size_t send_pending_encrypted_data(boost::system::error_code& ec)
+ {
+ if(ec)
+ { return 0; }
+
+ auto writtenBytes = boost::asio::write(m_nextLayer, send_buffer(), ec);
+ consume_send_buffer(writtenBytes);
+
+ if(ec == boost::asio::error::eof && !shutdown_received())
+ {
+ // transport layer was closed by peer without receiving 'close_notify'
+ ec = StreamError::StreamTruncated;
+ }
+
+ return writtenBytes;
+ }
+
+ /**
+ * @brief Pass plaintext data to the native handle for processing.
+ *
+ * The native handle will then create TLS records and hand them back to the Stream via the tls_emit_data callback.
+ */
+ template <typename ConstBufferSequence>
+ void tls_encrypt(const ConstBufferSequence& buffers, boost::system::error_code& ec)
+ {
+ // NOTE: This is not asynchronous: it encrypts the data synchronously.
+ // The data encrypted by native_handle()->send() is synchronously stored in the send_buffer of m_core,
+ // but is not actually written to the wire, yet.
+ for(auto it = boost::asio::buffer_sequence_begin(buffers);
+ !ec && it != boost::asio::buffer_sequence_end(buffers);
+ it++)
+ {
+ const boost::asio::const_buffer buffer = *it;
+ try_with_error_code([&]
+ {
+ native_handle()->send(static_cast<const uint8_t*>(buffer.data()), buffer.size());
+ }, ec);
+ }
+ }
+
+ /**
+ * @brief Pass encrypted data to the native handle for processing.
+ *
+ * If an exception occurs while processing the data, an error code will be set.
+ *
+ * @param read_buffer Input buffer containing the encrypted data.
+ * @param ec Set to indicate what error occurred, if any.
+ */
+ void process_encrypted_data(const boost::asio::const_buffer& read_buffer, boost::system::error_code& ec)
+ {
+ try_with_error_code([&]
+ {
+ native_handle()->received_data(static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size());
+ }, ec);
+ }
+
+ //! @brief Catch exceptions and set an error_code
+ template <typename Fun>
+ void try_with_error_code(Fun f, boost::system::error_code& ec)
+ {
+ try
+ {
+ f();
+ }
+ catch(const TLS_Exception& e)
+ {
+ ec = e.type();
+ }
+ catch(const Botan::Exception& e)
+ {
+ ec = e.error_type();
+ }
+ catch(const std::exception&)
+ {
+ ec = Botan::ErrorType::Unknown;
+ }
+ }
+
+ void set_shutdown_received()
+ {
+ m_shutdown_received = true;
+ }
+
+ Context& m_context;
+ StreamLayer m_nextLayer;
+
+ boost::beast::flat_buffer m_receive_buffer;
+ boost::beast::flat_buffer m_send_buffer;
+
+ StreamCore m_core;
+ std::unique_ptr<ChannelT> m_native_handle;
+
+ bool m_shutdown_received;
+
+ // Buffer space used to read input intended for the core
+ std::vector<uint8_t> m_input_buffer_space;
+ const boost::asio::mutable_buffer m_input_buffer;
+ };
+
+} // namespace TLS
+} // namespace Botan
+
+#endif // BOOST_VERSION
+#endif // BOTAN_ASIO_STREAM_H_
diff --git a/comm/third_party/botan/src/lib/tls/asio/info.txt b/comm/third_party/botan/src/lib/tls/asio/info.txt
new file mode 100644
index 0000000000..7862a1c284
--- /dev/null
+++ b/comm/third_party/botan/src/lib/tls/asio/info.txt
@@ -0,0 +1,15 @@
+<defines>
+TLS_ASIO_STREAM -> 20181218
+</defines>
+
+<header:public>
+asio_context.h
+asio_stream.h
+asio_error.h
+asio_async_ops.h
+</header:public>
+
+<requires>
+boost
+tls
+</requires>