This commit is contained in:
		@@ -1,300 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
* 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_all.h>
 | 
			
		||||
#include <asio.hpp>
 | 
			
		||||
#include <asio/yield.hpp>
 | 
			
		||||
#include "asio_error.h"
 | 
			
		||||
 | 
			
		||||
namespace Botan::TLS::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.assign(StreamError::StreamTruncated, std::generic_category());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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.assign(StreamError::StreamTruncated, std::generic_category());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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.assign(StreamError::StreamTruncated, std::generic_category());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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 Botan::TLS::detail
 | 
			
		||||
 | 
			
		||||
   #include <asio/unyield.hpp>
 | 
			
		||||
 | 
			
		||||
#endif  // BOTAN_ASIO_ASYNC_OPS_H_
 | 
			
		||||
@@ -1,90 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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_all.h>
 | 
			
		||||
#include <functional>
 | 
			
		||||
 | 
			
		||||
namespace Botan::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(std::shared_ptr<Credentials_Manager> credentials_manager,
 | 
			
		||||
              std::shared_ptr<RandomNumberGenerator> rng,
 | 
			
		||||
              std::shared_ptr<Session_Manager> session_manager,
 | 
			
		||||
              std::shared_ptr<const 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(std::move(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(Server_Information server_info) { m_server_info = std::move(server_info); }
 | 
			
		||||
 | 
			
		||||
   protected:
 | 
			
		||||
      template <class S, class C>
 | 
			
		||||
      friend class Stream;
 | 
			
		||||
 | 
			
		||||
      std::shared_ptr<Credentials_Manager> m_credentials_manager;
 | 
			
		||||
      std::shared_ptr<RandomNumberGenerator> m_rng;
 | 
			
		||||
      std::shared_ptr<Session_Manager> m_session_manager;
 | 
			
		||||
      std::shared_ptr<const Policy> m_policy;
 | 
			
		||||
 | 
			
		||||
      Server_Information m_server_info;
 | 
			
		||||
      Verify_Callback m_verify_callback;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace Botan::TLS
 | 
			
		||||
 | 
			
		||||
#endif  // BOTAN_ASIO_TLS_CONTEXT_H_
 | 
			
		||||
@@ -1,125 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
* 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_all.h>
 | 
			
		||||
#include <asio/error_code.hpp>
 | 
			
		||||
 | 
			
		||||
namespace boost{
 | 
			
		||||
    namespace asio = ::asio;
 | 
			
		||||
    namespace system {
 | 
			
		||||
        template <typename T>
 | 
			
		||||
        struct is_error_code_enum { static const bool value = false; };
 | 
			
		||||
 | 
			
		||||
        typedef asio::error_category error_category;
 | 
			
		||||
        typedef asio::error_code error_code;
 | 
			
		||||
    }
 | 
			
		||||
    namespace beast {
 | 
			
		||||
        using flat_buffer = asio::streambuf;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
      virtual ~StreamCategory() = default;
 | 
			
		||||
 | 
			
		||||
      const char* name() const noexcept override { return "Botan TLS Stream"; }
 | 
			
		||||
 | 
			
		||||
      std::string message(int value) const override {
 | 
			
		||||
         if(value == StreamTruncated) {
 | 
			
		||||
            return "stream truncated";
 | 
			
		||||
         } else {
 | 
			
		||||
            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 {
 | 
			
		||||
      virtual ~BotanAlertCategory() = default;
 | 
			
		||||
 | 
			
		||||
      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 {
 | 
			
		||||
      virtual ~BotanErrorCategory() = default;
 | 
			
		||||
 | 
			
		||||
      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::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 boost::system
 | 
			
		||||
 | 
			
		||||
#endif  // BOTAN_ASIO_ERROR_H_
 | 
			
		||||
@@ -1,740 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
* TLS ASIO Stream
 | 
			
		||||
* (C) 2018-2021 Jack Lloyd
 | 
			
		||||
*     2018-2021 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_all.h>
 | 
			
		||||
#include <asio.hpp>
 | 
			
		||||
 | 
			
		||||
#include "asio_async_ops.h"
 | 
			
		||||
#include "asio_context.h"
 | 
			
		||||
#include "asio_error.h"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
 | 
			
		||||
namespace Botan::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 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(std::shared_ptr<Context> context, Args&&... args) :
 | 
			
		||||
            m_context(context),
 | 
			
		||||
            m_nextLayer(std::forward<Args>(args)...),
 | 
			
		||||
            m_core(std::make_shared<StreamCore>(context)),
 | 
			
		||||
            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, std::shared_ptr<Context> context) :
 | 
			
		||||
            m_context(context),
 | 
			
		||||
            m_nextLayer(std::forward<Arg>(arg)),
 | 
			
		||||
            m_core(std::make_shared<StreamCore>(context)),
 | 
			
		||||
            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 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.assign((int)ErrorType::NotImplemented, std::generic_category());
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //! @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.assign((int)ErrorType::NotImplemented, std::generic_category());
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //! @}
 | 
			
		||||
      //! \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 == Connection_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 completion_token The completion handler to be called when the handshake operation completes.
 | 
			
		||||
       *                         The completion signature of the handler must be: void(boost::system::error_code).
 | 
			
		||||
       */
 | 
			
		||||
      template <typename CompletionToken>
 | 
			
		||||
      auto async_handshake(Botan::TLS::Connection_Side side, CompletionToken&& completion_token) {
 | 
			
		||||
         return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
 | 
			
		||||
            [this](auto&& completion_handler, TLS::Connection_Side connection_side) {
 | 
			
		||||
               using completion_handler_t = std::decay_t<decltype(completion_handler)>;
 | 
			
		||||
 | 
			
		||||
               ASIO_HANDSHAKE_HANDLER_CHECK(completion_handler_t, completion_handler) type_check;
 | 
			
		||||
 | 
			
		||||
               boost::system::error_code ec;
 | 
			
		||||
               setup_native_handle(connection_side, ec);
 | 
			
		||||
 | 
			
		||||
               detail::AsyncHandshakeOperation<completion_handler_t, Stream> op{
 | 
			
		||||
                  std::forward<completion_handler_t>(completion_handler), *this, ec};
 | 
			
		||||
            },
 | 
			
		||||
            completion_token,
 | 
			
		||||
            side);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //! @throws Not_Implemented
 | 
			
		||||
      template <typename ConstBufferSequence, typename BufferedHandshakeHandler>
 | 
			
		||||
      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);
 | 
			
		||||
         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 completion_token The completion handler to be called when the shutdown operation completes.
 | 
			
		||||
       *                         The completion signature of the handler must be: void(boost::system::error_code).
 | 
			
		||||
       */
 | 
			
		||||
      template <typename CompletionToken>
 | 
			
		||||
      auto async_shutdown(CompletionToken&& completion_token) {
 | 
			
		||||
         return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
 | 
			
		||||
            [this](auto&& completion_handler) {
 | 
			
		||||
               using completion_handler_t = std::decay_t<decltype(completion_handler)>;
 | 
			
		||||
 | 
			
		||||
               ASIO_SHUTDOWN_HANDLER_CHECK(completion_handler_t, completion_handler) type_check;
 | 
			
		||||
 | 
			
		||||
               boost::system::error_code ec;
 | 
			
		||||
               try_with_error_code([&] { native_handle()->close(); }, ec);
 | 
			
		||||
 | 
			
		||||
               using write_handler_t = Wrapper<completion_handler_t, typename Stream::executor_type>;
 | 
			
		||||
 | 
			
		||||
               TLS::detail::AsyncWriteOperation<write_handler_t, Stream> op{
 | 
			
		||||
                  write_handler_t{std::forward<completion_handler_t>(completion_handler), get_executor()},
 | 
			
		||||
                  *this,
 | 
			
		||||
                  boost::asio::buffer_size(send_buffer()),
 | 
			
		||||
                  ec};
 | 
			
		||||
            },
 | 
			
		||||
            completion_token);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //! @}
 | 
			
		||||
      //! \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.assign(StreamError::StreamTruncated, std::generic_category());
 | 
			
		||||
         }
 | 
			
		||||
 | 
			
		||||
         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;
 | 
			
		||||
         const auto 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;
 | 
			
		||||
         const auto 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 completion_token The completion handler to be called when the write operation completes. Copies of the
 | 
			
		||||
       *                         handler will be made as required. The completion signature of the handler must be:
 | 
			
		||||
       *                         void(boost::system::error_code, std::size_t).
 | 
			
		||||
       */
 | 
			
		||||
      template <typename ConstBufferSequence, typename CompletionToken>
 | 
			
		||||
      auto async_write_some(const ConstBufferSequence& buffers, CompletionToken&& completion_token) {
 | 
			
		||||
         return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code, std::size_t)>(
 | 
			
		||||
            [this](auto&& completion_handler, const auto& bufs) {
 | 
			
		||||
               using completion_handler_t = std::decay_t<decltype(completion_handler)>;
 | 
			
		||||
 | 
			
		||||
               ASIO_WRITE_HANDLER_CHECK(completion_handler_t, completion_handler) type_check;
 | 
			
		||||
 | 
			
		||||
               boost::system::error_code ec;
 | 
			
		||||
               tls_encrypt(bufs, 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_core->send_buffer.size());
 | 
			
		||||
               }
 | 
			
		||||
 | 
			
		||||
               detail::AsyncWriteOperation<completion_handler_t, Stream> op{
 | 
			
		||||
                  std::forward<completion_handler_t>(completion_handler),
 | 
			
		||||
                  *this,
 | 
			
		||||
                  ec ? 0 : boost::asio::buffer_size(bufs),
 | 
			
		||||
                  ec};
 | 
			
		||||
            },
 | 
			
		||||
            completion_token,
 | 
			
		||||
            buffers);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      /**
 | 
			
		||||
       * @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 completion_token The completion handler to be called when the read operation completes. The completion
 | 
			
		||||
       *                         signature of the handler must be: void(boost::system::error_code, std::size_t).
 | 
			
		||||
       */
 | 
			
		||||
      template <typename MutableBufferSequence, typename CompletionToken>
 | 
			
		||||
      auto async_read_some(const MutableBufferSequence& buffers, CompletionToken&& completion_token) {
 | 
			
		||||
         return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code, std::size_t)>(
 | 
			
		||||
            [this](auto&& completion_handler, const auto& bufs) {
 | 
			
		||||
               using completion_handler_t = std::decay_t<decltype(completion_handler)>;
 | 
			
		||||
 | 
			
		||||
               ASIO_READ_HANDLER_CHECK(completion_handler_t, completion_handler) type_check;
 | 
			
		||||
 | 
			
		||||
               detail::AsyncReadOperation<completion_handler_t, Stream, MutableBufferSequence> op{
 | 
			
		||||
                  std::forward<completion_handler_t>(completion_handler), *this, bufs};
 | 
			
		||||
            },
 | 
			
		||||
            completion_token,
 | 
			
		||||
            buffers);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //! @}
 | 
			
		||||
 | 
			
		||||
      //! @brief Indicates whether a close_notify alert has been received from the peer.
 | 
			
		||||
      //!
 | 
			
		||||
      //! Note that we cannot m_core.is_closed_for_reading() because this wants to
 | 
			
		||||
      //! explicitly check that the peer sent close_notify.
 | 
			
		||||
      bool shutdown_received() const { return m_core->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 TLS::Callbacks
 | 
			
		||||
       *
 | 
			
		||||
       * This class is provided to the stream's native_handle (TLS::Channel) and implements the callback
 | 
			
		||||
       * functions triggered by the native_handle.
 | 
			
		||||
       */
 | 
			
		||||
      class StreamCore : public TLS::Callbacks {
 | 
			
		||||
         public:
 | 
			
		||||
            StreamCore(std::weak_ptr<Botan::TLS::Context> context) : shutdown_received(false), m_context(context) {}
 | 
			
		||||
 | 
			
		||||
            ~StreamCore() override = default;
 | 
			
		||||
 | 
			
		||||
            void tls_emit_data(std::span<const uint8_t> data) override {
 | 
			
		||||
               send_buffer.commit(boost::asio::buffer_copy(send_buffer.prepare(data.size()),
 | 
			
		||||
                                                           boost::asio::buffer(data.data(), data.size())));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            void tls_record_received(uint64_t, std::span<const uint8_t> data) override {
 | 
			
		||||
               receive_buffer.commit(boost::asio::buffer_copy(receive_buffer.prepare(data.size()),
 | 
			
		||||
                                                              boost::asio::const_buffer(data.data(), data.size())));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            bool tls_peer_closed_connection() override {
 | 
			
		||||
               // Instruct the TLS implementation to reply with our close_notify to obtain
 | 
			
		||||
               // the same behaviour for TLS 1.2 and TLS 1.3.
 | 
			
		||||
               return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            void tls_alert(TLS::Alert alert) override {
 | 
			
		||||
               if(alert.type() == TLS::AlertType::CloseNotify) {
 | 
			
		||||
                  shutdown_received = true;
 | 
			
		||||
                  // 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);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            void tls_verify_cert_chain(const std::vector<X509_Certificate>& cert_chain,
 | 
			
		||||
                                       const std::vector<std::optional<OCSP::Response>>& ocsp_responses,
 | 
			
		||||
                                       const std::vector<Certificate_Store*>& trusted_roots,
 | 
			
		||||
                                       Usage_Type usage,
 | 
			
		||||
                                       std::string_view hostname,
 | 
			
		||||
                                       const TLS::Policy& policy) override {
 | 
			
		||||
               auto ctx = m_context.lock();
 | 
			
		||||
 | 
			
		||||
               if(ctx && ctx->has_verify_callback()) {
 | 
			
		||||
                  ctx->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);
 | 
			
		||||
               }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            bool shutdown_received;
 | 
			
		||||
            boost::beast::flat_buffer receive_buffer;
 | 
			
		||||
            boost::beast::flat_buffer send_buffer;
 | 
			
		||||
 | 
			
		||||
         private:
 | 
			
		||||
            std::weak_ptr<TLS::Context> m_context;
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      const boost::asio::mutable_buffer& input_buffer() { return m_input_buffer; }
 | 
			
		||||
 | 
			
		||||
      boost::asio::const_buffer send_buffer() const { return m_core->send_buffer.data(); }
 | 
			
		||||
 | 
			
		||||
      //! @brief Check if decrypted data is available in the receive buffer
 | 
			
		||||
      bool has_received_data() const { return m_core->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_core->receive_buffer.data());
 | 
			
		||||
         m_core->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_core->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_core->send_buffer.consume(bytesConsumed); }
 | 
			
		||||
 | 
			
		||||
      /**
 | 
			
		||||
       * @brief Create the native handle.
 | 
			
		||||
       *
 | 
			
		||||
       * Depending on the desired connection side, this function will create a TLS::Client or a
 | 
			
		||||
       * TLS::Server.
 | 
			
		||||
       *
 | 
			
		||||
       * @param side The desired connection side (client or server)
 | 
			
		||||
       * @param ec Set to indicate what error occurred, if any.
 | 
			
		||||
       */
 | 
			
		||||
      void setup_native_handle(Connection_Side side, boost::system::error_code& ec) {
 | 
			
		||||
         BOTAN_UNUSED(side);  // workaround: GCC 9 produces a warning claiming side is unused
 | 
			
		||||
 | 
			
		||||
         // Do not attempt to instantiate the native_handle when a custom (mocked) channel type template parameter has
 | 
			
		||||
         // been specified. This allows mocking the native_handle in test code.
 | 
			
		||||
         if constexpr(std::is_same<ChannelT, Channel>::value) {
 | 
			
		||||
            try_with_error_code(
 | 
			
		||||
               [&] {
 | 
			
		||||
                  if(side == Connection_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,
 | 
			
		||||
                                   m_context->m_policy->latest_supported_version(false /* no DTLS */)));
 | 
			
		||||
                  } 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.assign(StreamError::StreamTruncated, std::generic_category());
 | 
			
		||||
         }
 | 
			
		||||
 | 
			
		||||
         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) {
 | 
			
		||||
          f();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      std::shared_ptr<Context> m_context;
 | 
			
		||||
      StreamLayer m_nextLayer;
 | 
			
		||||
 | 
			
		||||
      std::shared_ptr<StreamCore> m_core;
 | 
			
		||||
      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;
 | 
			
		||||
      const boost::asio::mutable_buffer m_input_buffer;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace Botan::TLS
 | 
			
		||||
 | 
			
		||||
#endif  // BOTAN_ASIO_STREAM_H_
 | 
			
		||||
		Reference in New Issue
	
	Block a user