From 1974bccaddb9fa0ffcea8080178e0af98bff4c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20J=C5=AFza?= Date: Wed, 3 Jul 2019 09:30:42 +0200 Subject: [PATCH 1/5] Try this to export asio headers without boost --- src/lib/tls/info.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/tls/info.txt b/src/lib/tls/info.txt index 5fe9572177e..7204bf2b906 100644 --- a/src/lib/tls/info.txt +++ b/src/lib/tls/info.txt @@ -22,6 +22,10 @@ tls_server.h tls_session.h tls_session_manager.h tls_version.h +asio/asio_async_ops.h +asio/asio_context.h +asio/asio_error.h +asio/asio_stream.h From b2ee5cc3864b16fc7fd9963fac527d5186826e2c Mon Sep 17 00:00:00 2001 From: Jakub Juza Date: Wed, 3 Jul 2019 08:30:01 +0000 Subject: [PATCH 2/5] append asio libs in BuildPaths in configure --- configure.py | 5 +++++ src/lib/tls/info.txt | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/configure.py b/configure.py index b892389e384..f4cbff5407b 100755 --- a/configure.py +++ b/configure.py @@ -213,6 +213,11 @@ def __init__(self, source_paths, options, modules): self.public_headers = sorted(flatten([m.public_headers() for m in modules])) + self.public_headers.append('./src/lib/tls/asio/asio_async_ops.h') + self.public_headers.append('./src/lib/tls/asio/asio_context.h') + self.public_headers.append('./src/lib/tls/asio/asio_error.h') + self.public_headers.append('./src/lib/tls/asio/asio_stream.h') + def find_sources_in(basedir, srcdir): for (dirpath, _, filenames) in os.walk(os.path.join(basedir, srcdir)): for filename in filenames: diff --git a/src/lib/tls/info.txt b/src/lib/tls/info.txt index 7204bf2b906..5fe9572177e 100644 --- a/src/lib/tls/info.txt +++ b/src/lib/tls/info.txt @@ -22,10 +22,6 @@ tls_server.h tls_session.h tls_session_manager.h tls_version.h -asio/asio_async_ops.h -asio/asio_context.h -asio/asio_error.h -asio/asio_stream.h From e0aac42e2ed88886ac7f13d8e1d4f81a715e9c3b Mon Sep 17 00:00:00 2001 From: Jakub Juza Date: Wed, 3 Jul 2019 09:02:31 +0000 Subject: [PATCH 3/5] Try to add the asio files into tls info file --- configure.py | 5 ----- src/lib/tls/info.txt | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/configure.py b/configure.py index f4cbff5407b..b892389e384 100755 --- a/configure.py +++ b/configure.py @@ -213,11 +213,6 @@ def __init__(self, source_paths, options, modules): self.public_headers = sorted(flatten([m.public_headers() for m in modules])) - self.public_headers.append('./src/lib/tls/asio/asio_async_ops.h') - self.public_headers.append('./src/lib/tls/asio/asio_context.h') - self.public_headers.append('./src/lib/tls/asio/asio_error.h') - self.public_headers.append('./src/lib/tls/asio/asio_stream.h') - def find_sources_in(basedir, srcdir): for (dirpath, _, filenames) in os.walk(os.path.join(basedir, srcdir)): for filename in filenames: diff --git a/src/lib/tls/info.txt b/src/lib/tls/info.txt index 5fe9572177e..7204bf2b906 100644 --- a/src/lib/tls/info.txt +++ b/src/lib/tls/info.txt @@ -22,6 +22,10 @@ tls_server.h tls_session.h tls_session_manager.h tls_version.h +asio/asio_async_ops.h +asio/asio_context.h +asio/asio_error.h +asio/asio_stream.h From ac96889eba5f1e64aed8a6977b88c4ca128fccca Mon Sep 17 00:00:00 2001 From: "jan.capek" Date: Thu, 4 Jul 2019 09:17:37 +0200 Subject: [PATCH 4/5] Update Server --- src/lib/tls/asio/asio_async_ops.h | 595 +++++++------ src/lib/tls/asio/asio_context.h | 173 ++-- src/lib/tls/asio/asio_error.h | 139 +-- src/lib/tls/asio/asio_stream.h | 1322 ++++++++++++++--------------- 4 files changed, 1123 insertions(+), 1106 deletions(-) diff --git a/src/lib/tls/asio/asio_async_ops.h b/src/lib/tls/asio/asio_async_ops.h index 3520db770cb..26c5096e2cf 100644 --- a/src/lib/tls/asio/asio_async_ops.h +++ b/src/lib/tls/asio/asio_async_ops.h @@ -10,8 +10,8 @@ #define BOTAN_ASIO_ASYNC_OPS_H_ #include - #include + #if BOOST_VERSION >= 106600 #include @@ -19,12 +19,13 @@ // We need to define BOOST_ASIO_DISABLE_SERIAL_PORT before any asio imports. Otherwise asio will include , // which interferes with Botan's amalgamation by defining macros like 'B0' and 'FF1'. #define BOOST_ASIO_DISABLE_SERIAL_PORT + #include #include namespace Botan { -namespace TLS { -namespace detail { + namespace TLS { + namespace detail { /** * Base class for asynchronous stream operations. @@ -64,302 +65,298 @@ namespace detail { * \tparam Executor1 Type of the asio executor (usually derived from the lower layer) * \tparam Allocator Type of the allocator to be used */ -template -class AsyncBase : public boost::asio::coroutine - { - public: - using allocator_type = boost::asio::associated_allocator_t; - using executor_type = boost::asio::associated_executor_t; - - 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 - AsyncBase(HandlerT&& handler, const Executor1& executor) - : m_handler(std::forward(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 - void complete_now(Args&& ... args) - { - m_work_guard_1.reset(); - m_handler(std::forward(args)...); - } - - Handler m_handler; - boost::asio::executor_work_guard m_work_guard_1; - }; - -template > -class AsyncReadOperation : public AsyncBase - { - 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 - AsyncReadOperation(HandlerT&& handler, - Stream& stream, - const MutableBufferSequence& buffers, - const boost::system::error_code& ec = {}) - : AsyncBase( - std::forward(handler), - stream.get_executor()) - , m_stream(stream) - , m_buffers(buffers) - , m_decodedBytes(0) - { - this->operator()(ec, std::size_t(0), false); - } - - AsyncReadOperation(AsyncReadOperation&&) = default; - - void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true) - { - reenter(this) - { - if(bytes_transferred > 0 && !ec) - { - // We have received encrypted data from the network, now hand it to TLS::Channel for decryption. - boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytes_transferred}; - try - { - m_stream.native_handle()->received_data( - static_cast(read_buffer.data()), read_buffer.size() - ); - } - catch(const TLS_Exception& e) - { - ec = e.type(); - } - catch(const Botan::Exception& e) - { - ec = e.error_type(); - } - catch(...) - { - ec = Botan::ErrorType::Unknown; - } - } - - if(!m_stream.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 > -class AsyncWriteOperation : public AsyncBase - { - 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 - AsyncWriteOperation(HandlerT&& handler, - Stream& stream, - std::size_t plainBytesTransferred, - const boost::system::error_code& ec = {}) - : AsyncBase( - std::forward(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(!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 AsyncHandshakeOperation : public AsyncBase - { - 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 - AsyncHandshakeOperation( - HandlerT&& handler, - Stream& stream, - const boost::system::error_code& ec = {}) - : AsyncBase( - std::forward(handler), - stream.get_executor()) - , m_stream(stream) - { - this->operator()(ec, std::size_t(0), false); - } - - AsyncHandshakeOperation(AsyncHandshakeOperation&&) = default; - - void operator()(boost::system::error_code ec, std::size_t bytesTransferred, bool isContinuation = true) - { - reenter(this) - { - if(bytesTransferred > 0 && !ec) - { - // Provide encrypted TLS data received from the network to TLS::Channel for decryption - boost::asio::const_buffer read_buffer {m_stream.input_buffer().data(), bytesTransferred}; - try - { - m_stream.native_handle()->received_data( - static_cast(read_buffer.data()), read_buffer.size() - ); - } - catch(const TLS_Exception& e) - { - ec = e.type(); - } - catch(const Botan::Exception& e) - { - ec = e.error_type(); - } - catch(...) - { - ec = Botan::ErrorType::Unknown; - } - } - - if(m_stream.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::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 + template + class AsyncBase : public boost::asio::coroutine { + public: + using allocator_type = boost::asio::associated_allocator_t; + using executor_type = boost::asio::associated_executor_t; + + 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 + AsyncBase(HandlerT&& handler, const Executor1& executor) + : m_handler(std::forward(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 + void complete_now(Args&& ... args) { + m_work_guard_1.reset(); + m_handler(std::forward(args)...); + } + + Handler m_handler; + boost::asio::executor_work_guard m_work_guard_1; + }; + + template> + class AsyncReadOperation : public AsyncBase { + 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 + AsyncReadOperation(HandlerT&& handler, + Stream& stream, + const MutableBufferSequence& buffers, + const boost::system::error_code& ec = {}) + : AsyncBase( + std::forward(handler), + stream.get_executor()), m_stream(stream), m_buffers(buffers), m_decodedBytes(0) { + this->operator()(ec, std::size_t(0), false); + } + + AsyncReadOperation(AsyncReadOperation&&) = default; + + void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true) { + reenter(this) { + if (bytes_transferred > 0 && !ec) { + // We have received encrypted data from the network, now hand it to TLS::Channel for decryption. + boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytes_transferred}; + try { + m_stream.native_handle()->received_data( + static_cast(read_buffer.data()), read_buffer.size() + ); + } + catch (const TLS_Exception& e) { + ec = e.type(); + } + catch (const Botan::Exception& e) { + ec = e.error_type(); + } + catch (...) { + ec = Botan::ErrorType::Unknown; + } + } + + if (!m_stream.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> + class AsyncWriteOperation : public AsyncBase { + 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 + AsyncWriteOperation(HandlerT&& handler, + Stream& stream, + std::size_t plainBytesTransferred, + const boost::system::error_code& ec = {}) + : AsyncBase( + std::forward(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 (!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 AsyncHandshakeOperation : public AsyncBase { + 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 + AsyncHandshakeOperation( + HandlerT&& handler, + Stream& stream, + const boost::system::error_code& ec = {}) + : AsyncBase( + std::forward(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) { + using AWO = AsyncWriteOperation< + AsyncHandshakeOperation::type, Stream, Allocator>, + Stream, + Allocator>; + if (bytesTransferred > 0) { + // Provide encrypted TLS data received from the network to TLS::Channel for decryption + boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytesTransferred}; + try { + m_stream.native_handle()->received_data( + static_cast(read_buffer.data()), read_buffer.size() + ); + + if (m_stream.m_core.m_isAlerted) { + if(m_stream.m_core.m_alert.is_fatal()){ + m_has_exception_cat = true; + ec = boost::system::error_code(m_stream.m_core.m_alert.type(), BotanAlertCategory()); + m_last_cat = BotanExceptionCategory(ec.message()); + m_stream.native_handle()->close(); + AWO op{std::move(*this), m_stream, 0}; + return; + }else{ + m_stream.native_handle()->send_alert(m_stream.m_core.m_alert); + m_stream.m_core.m_isAlerted = false; + } + + } + } + catch (const TLS_Exception& e) { + m_has_exception_cat = true; + m_last_cat = BotanExceptionCategory(e.what()); + m_stream.native_handle()->close(); + AWO op{std::move(*this), m_stream, 0}; + return; + } + catch (const Botan::Exception& e) { + m_has_exception_cat = true; + m_last_cat = BotanExceptionCategory(e.what()); + m_stream.native_handle()->close(); + AWO op{std::move(*this), m_stream, 0}; + return; + } + catch (...) { + m_has_exception_cat = true; + m_last_cat = BotanExceptionCategory("Unknown exception"); + m_stream.native_handle()->close(); + AWO op{std::move(*this), m_stream, 0}; + return; + } + } + + 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. + AWO op{std::move(*this), m_stream, 0}; + return; + } + + if (!m_stream.native_handle()->is_active() && !ec && !m_has_exception_cat) { + // 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; + } + + if (m_has_exception_cat) { + boost::system::error_code errc(1, m_last_cat); + this->complete_now(errc); + m_stream.next_layer().close(); + } else { + this->complete_now(ec); + } + } + } + + private: + Stream& m_stream; + BotanExceptionCategory m_last_cat{""}; + bool m_has_exception_cat = false; + boost::system::error_code m_ec; + }; + + } // namespace detail + } // namespace TLS } // namespace Botan #include diff --git a/src/lib/tls/asio/asio_context.h b/src/lib/tls/asio/asio_context.h index e5e99e83a9c..44662d772b5 100644 --- a/src/lib/tls/asio/asio_context.h +++ b/src/lib/tls/asio/asio_context.h @@ -12,6 +12,7 @@ #include #include + #if BOOST_VERSION >= 106600 #include @@ -25,95 +26,101 @@ #include namespace Botan { -namespace TLS { + namespace TLS { -namespace detail { -template -struct fn_signature_helper : public std::false_type {}; + namespace detail { + template + struct fn_signature_helper : public std::false_type { + }; -template -struct fn_signature_helper - { - using type = std::function; - }; -} // namespace detail + template + struct fn_signature_helper { + using type = std::function; + }; + } // 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::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(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 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 + 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::type; + + using TLSSessionEstablished_Callback = std::function; + + 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); + } + + void set_tls_established_callback(TLSSessionEstablished_Callback callback) { + m_tls_established_callback = std::move(callback); + } + + bool has_verify_callback() const { + return static_cast(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 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; + TLSSessionEstablished_Callback m_tls_established_callback; + }; + + } // namespace TLS } // namespace Botan #endif // BOOST_VERSION diff --git a/src/lib/tls/asio/asio_error.h b/src/lib/tls/asio/asio_error.h index 7356ab3d748..487e32911b1 100644 --- a/src/lib/tls/asio/asio_error.h +++ b/src/lib/tls/asio/asio_error.h @@ -12,6 +12,7 @@ #include #include + #if BOOST_VERSION >= 106600 #include @@ -21,77 +22,95 @@ #include namespace Botan { -namespace TLS { + namespace TLS { //! @brief An error category for TLS alerts -struct BotanAlertCategory : boost::system::error_category - { - const char* name() const noexcept override - { - return "asio.botan.tls.alert"; - } - - std::string message(int ev) const override - { - Botan::TLS::Alert alert(static_cast(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(c), Botan::TLS::botan_alert_category()); - } - -} // namespace TLS + struct BotanAlertCategory : boost::system::error_category { + const char* name() const noexcept override { + return "asio.botan.tls.alert"; + } + + std::string message(int ev) const override { + Botan::TLS::Alert alert(static_cast(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(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 "asio.botan.tls"; - } - - std::string message(int ev) const override - { - return Botan::to_string(static_cast(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(e), Botan::botan_category()); - } + struct BotanErrorCategory : boost::system::error_category { + const char* name() const noexcept override { + return "asio.botan.tls"; + } + + std::string message(int ev) const override { + return Botan::to_string(static_cast(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(e), Botan::botan_category()); + } + + + struct BotanExceptionCategory : boost::system::error_category { + BotanExceptionCategory(std::string message) : message_(std::move(message)) { + + } + + BotanExceptionCategory(const Botan::BotanExceptionCategory& ref){ + this->message_ = ref.message_; + } + + virtual ~BotanExceptionCategory(){ + + } + + Botan::BotanExceptionCategory& operator=(const Botan::BotanExceptionCategory& rhs){ + this->message_ = rhs.message_; + } + + const char* name() const noexcept override { + return "asio.botan.exception"; + } + + std::string message(int) const override { + return message_; + } + std::string message_; + }; } // namespace Botan namespace boost { -namespace system { + namespace system { -template<> struct is_error_code_enum - { - static const bool value = true; - }; + template<> + struct is_error_code_enum { + static const bool value = true; + }; -template<> struct is_error_code_enum - { - static const bool value = true; - }; + template<> + struct is_error_code_enum { + static const bool value = true; + }; -} // namespace system + } // namespace system } // namespace boost #endif // BOOST_VERSION diff --git a/src/lib/tls/asio/asio_stream.h b/src/lib/tls/asio/asio_stream.h index e8d9c29301f..f1cfe042214 100644 --- a/src/lib/tls/asio/asio_stream.h +++ b/src/lib/tls/asio/asio_stream.h @@ -13,6 +13,7 @@ // first version to be compatible with Networking TS (N4656) and boost::beast #include + #if BOOST_VERSION >= 106600 #include @@ -23,10 +24,12 @@ #include #include #include +#include // We need to define BOOST_ASIO_DISABLE_SERIAL_PORT before any asio imports. Otherwise asio will include , // which interferes with Botan's amalgamation by defining macros like 'B0' and 'FF1'. #define BOOST_ASIO_DISABLE_SERIAL_PORT + #include #include @@ -35,7 +38,7 @@ #include namespace Botan { -namespace TLS { + namespace TLS { /** * @brief boost::asio compatible SSL/TLS stream @@ -45,669 +48,660 @@ namespace TLS { * @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 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 - explicit Stream(Context& context, Args&& ... args) - : m_context(context) - , m_nextLayer(std::forward(args)...) - , m_core(m_receive_buffer, m_send_buffer, m_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 - explicit Stream(Arg&& arg, Context& context) - : m_context(context) - , m_nextLayer(std::forward(arg)) - , m_core(m_receive_buffer, m_send_buffer, m_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::type; - using lowest_layer_type = typename next_layer_type::lowest_layer_type; - using executor_type = typename next_layer_type::executor_type; - using native_handle_type = typename std::add_pointer::type; - - executor_type get_executor() noexcept { return m_nextLayer.get_executor(); } - - const next_layer_type& next_layer() const { return m_nextLayer; } - next_layer_type& next_layer() { return m_nextLayer; } - - lowest_layer_type& lowest_layer() { return m_nextLayer.lowest_layer(); } - const lowest_layer_type& lowest_layer() const { return m_nextLayer.lowest_layer(); } - - native_handle_type native_handle() { return m_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 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 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 - void set_verify_mode(verify_mode v) - { - BOTAN_UNUSED(v); - throw Not_Implemented("set_verify_mode is not implemented"); - } - - /** - * Not Implemented. - * @param ec Will be set to `Botan::ErrorType::NotImplemented` - */ - template - 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 type The type of handshaking to be performed, i.e. as a client or as a server. - * @throws boost::system::system_error if error occured, or if the chosen Connection_Side is not available - */ - 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 type The type of handshaking to be performed, i.e. as a client or as a server. - * @param ec Set to indicate what error occurred, if any. - */ - void handshake(Connection_Side side, boost::system::error_code& ec) - { - setup_native_handle(side, ec); - - // 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; } - - try - { - native_handle()->received_data(static_cast(read_buffer.data()), read_buffer.size()); - } - 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; - } - - send_pending_encrypted_data(ec); - } - } - - /** - * @brief Starts an asynchronous SSL handshake. - * - * This function call always returns immediately. - * - * @param type The type of handshaking to be performed, i.e. as a client or as a server. - * @param handler The handler to be called when the handshake operation completes. - * The equivalent function signature of the handler must be: void(boost::system::error_code) - * @throws NotImplemented if Connection_Side is not CLIENT - */ - template - BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, - void(boost::system::error_code)) - async_handshake(Connection_Side side, HandshakeHandler&& handler) - { - 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 init(handler); - - detail::AsyncHandshakeOperation::type, Stream> - op{std::move(init.completion_handler), *this, ec}; - - return init.result.get(); - } - - //! @throws Not_Implemented - template - 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. - * - * @param ec Set to indicate what error occured, if any. - */ - void shutdown(boost::system::error_code& ec) - { - try - { - native_handle()->close(); - } - 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; - } - - if(!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. - * - * @throws boost::system::system_error if error occured - */ - void shutdown() - { - boost::system::error_code ec; - shutdown(ec); - boost::asio::detail::throw_error(ec, "shutdown"); - } - - /** - * @brief Asynchronously shut down SSL on the stream. - * - * This function call always returns immediately. - * - * @param handler The handler to be called when the handshake operation completes. - * The equivalent function signature of the handler must be: void(boost::system::error_code) - */ - template - void async_shutdown(ShutdownHandler&& handler) - { - BOOST_ASIO_HANDSHAKE_HANDLER_CHECK(ShutdownHandler, handler) type_check; - BOTAN_UNUSED(handler); - throw Not_Implemented("async shutdown is not implemented"); - // TODO: Implement a subclass of AsyncBase that calls native_handle()->close() and writes pending data from - // the core to the network, e.g. using AsyncWriteOperation. - } - - //! @} - //! \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 occured, if any. - * @return The number of bytes read. Returns 0 if an error occurred. - */ - template - 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; } - - try - { - native_handle()->received_data(static_cast(read_buffer.data()), read_buffer.size()); - } - 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; - } - - 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 - 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 - 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 - 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 - BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, - void(boost::system::error_code, std::size_t)) - async_write_some(const ConstBufferSequence& buffers, WriteHandler&& handler) - { - BOOST_ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; - - boost::asio::async_completion 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::type, Stream> - op{std::move(init.completion_handler), *this, std::size_t(0), ec}; - return init.result.get(); - } - - detail::AsyncWriteOperation::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 - BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, - void(boost::system::error_code, std::size_t)) - async_read_some(const MutableBufferSequence& buffers, ReadHandler&& handler) - { - BOOST_ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; - - boost::asio::async_completion init(handler); - - detail::AsyncReadOperation::type, Stream, MutableBufferSequence> - op{std::move(init.completion_handler), *this, buffers}; - return init.result.get(); - } - - //! @} - - protected: - template friend class detail::AsyncReadOperation; - template friend class detail::AsyncWriteOperation; - template 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(boost::beast::flat_buffer& receive_buffer, boost::beast::flat_buffer& send_buffer, Context& context) - : m_receive_buffer(receive_buffer), m_send_buffer(send_buffer), m_tls_context(context) {} - - virtual ~StreamCore() = default; - - void tls_emit_data(const uint8_t data[], std::size_t size) override - { - m_send_buffer.commit( - boost::asio::buffer_copy(m_send_buffer.prepare(size), boost::asio::buffer(data, size)) - ); - } - - void tls_record_received(uint64_t, const uint8_t data[], std::size_t size) override - { - m_receive_buffer.commit( - boost::asio::buffer_copy(m_receive_buffer.prepare(size), boost::asio::const_buffer(data, size)) - ); - } - - void tls_alert(Botan::TLS::Alert alert) override - { - BOTAN_UNUSED(alert); - } - - 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& cert_chain, - const std::vector>& ocsp_responses, - const std::vector& trusted_roots, - Usage_Type usage, - const std::string& hostname, - const TLS::Policy& policy) override - { - if(m_tls_context.has_verify_callback()) - { - m_tls_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); - } - } - - boost::beast::flat_buffer& m_receive_buffer; - boost::beast::flat_buffer& m_send_buffer; - Context& m_tls_context; - }; - - 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 - 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 - typename std::enable_if::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 NotImplemented when side is SERVER - currently only CLIENT is implemented - */ - template - typename std::enable_if::value>::type - setup_native_handle(Connection_Side side, boost::system::error_code& ec) - { - if(side == CLIENT) - { - m_native_handle = std::unique_ptr( - 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)); - } - else - { - // TODO: First steps in order to support the server side of this stream would be to instantiate a - // Botan::TLS::Server instance as the stream's native_handle and implement the handshake appropriately. - ec = Botan::ErrorType::NotImplemented; - } - } - - 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); - return writtenBytes; - } - - template - 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 - { - native_handle()->send(static_cast(buffer.data()), buffer.size()); - } - 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; - } - } - } - - 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 m_native_handle; - - // Buffer space used to read input intended for the core - std::vector m_input_buffer_space; - const boost::asio::mutable_buffer m_input_buffer; - }; - -} // namespace TLS + template + 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 + explicit Stream(Context& context, Args&& ... args) + : m_context(context), m_nextLayer(std::forward(args)...), m_core(m_receive_buffer, m_send_buffer, m_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 + explicit Stream(Arg&& arg, Context& context) + : m_context(context), m_nextLayer(std::forward(arg)), m_core(m_receive_buffer, m_send_buffer, m_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::type; + using lowest_layer_type = typename next_layer_type::lowest_layer_type; + using executor_type = typename next_layer_type::executor_type; + using native_handle_type = typename std::add_pointer::type; + + executor_type get_executor() noexcept { + return m_nextLayer.get_executor(); + } + + const next_layer_type& next_layer() const { + return m_nextLayer; + } + + next_layer_type& next_layer() { + return m_nextLayer; + } + + lowest_layer_type& lowest_layer() { + return m_nextLayer.lowest_layer(); + } + + const lowest_layer_type& lowest_layer() const { + return m_nextLayer.lowest_layer(); + } + + native_handle_type native_handle() { + return m_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 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 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 + void set_verify_mode(verify_mode v) { + BOTAN_UNUSED(v); + throw Not_Implemented("set_verify_mode is not implemented"); + } + + /** + * Not Implemented. + * @param ec Will be set to `Botan::ErrorType::NotImplemented` + */ + template + 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 type The type of handshaking to be performed, i.e. as a client or as a server. + * @throws boost::system::system_error if error occured, or if the chosen Connection_Side is not available + */ + 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 type The type of handshaking to be performed, i.e. as a client or as a server. + * @param ec Set to indicate what error occurred, if any. + */ + void handshake(Connection_Side side, boost::system::error_code& ec) { + setup_native_handle(side, ec); + + // 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; + } + + try { + native_handle()->received_data(static_cast(read_buffer.data()), read_buffer.size()); + } + 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; + } + boost::system::error_code errc; + send_pending_encrypted_data(errc); + + if (errc) { + return; + } + } + } + + /** + * @brief Starts an asynchronous SSL handshake. + * + * This function call always returns immediately. + * + * @param type The type of handshaking to be performed, i.e. as a client or as a server. + * @param handler The handler to be called when the handshake operation completes. + * The equivalent function signature of the handler must be: void(boost::system::error_code) + * @throws NotImplemented if Connection_Side is not CLIENT + */ + template + BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, + void(boost::system::error_code)) + async_handshake(Connection_Side side, HandshakeHandler&& handler) { + 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 init(handler); + + detail::AsyncHandshakeOperation::type, Stream> + op{std::move(init.completion_handler), *this, ec}; + + return init.result.get(); + } + + //! @throws Not_Implemented + template + 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. + * + * @param ec Set to indicate what error occured, if any. + */ + void shutdown(boost::system::error_code& ec) { + try { + native_handle()->close(); + } + 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; + } + + if (!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. + * + * @throws boost::system::system_error if error occured + */ + void shutdown() { + boost::system::error_code ec; + shutdown(ec); + boost::asio::detail::throw_error(ec, "shutdown"); + } + + /** + * @brief Asynchronously shut down SSL on the stream. + * + * This function call always returns immediately. + * + * @param handler The handler to be called when the handshake operation completes. + * The equivalent function signature of the handler must be: void(boost::system::error_code) + */ + template + void async_shutdown(ShutdownHandler&& handler) { + BOOST_ASIO_HANDSHAKE_HANDLER_CHECK(ShutdownHandler, handler) type_check; + BOTAN_UNUSED(handler); + throw Not_Implemented("async shutdown is not implemented"); + // TODO: Implement a subclass of AsyncBase that calls native_handle()->close() and writes pending data from + // the core to the network, e.g. using AsyncWriteOperation. + } + + //! @} + //! \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 occured, if any. + * @return The number of bytes read. Returns 0 if an error occurred. + */ + template + 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; + } + + try { + native_handle()->received_data(static_cast(read_buffer.data()), read_buffer.size()); + } + 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; + } + + 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 + 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 + 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 + 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 + BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, + void(boost::system::error_code, std::size_t)) + async_write_some(const ConstBufferSequence& buffers, WriteHandler&& handler) { + BOOST_ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; + + boost::asio::async_completion 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::type, Stream> + op{std::move(init.completion_handler), *this, std::size_t(0), ec}; + return init.result.get(); + } + + detail::AsyncWriteOperation::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 + BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, + void(boost::system::error_code, std::size_t)) + async_read_some(const MutableBufferSequence& buffers, ReadHandler&& handler) { + BOOST_ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; + + boost::asio::async_completion init(handler); + + detail::AsyncReadOperation::type, Stream, MutableBufferSequence> + op{std::move(init.completion_handler), *this, buffers}; + return init.result.get(); + } + + //! @} + + protected: + template friend + class detail::AsyncReadOperation; + + template friend + class detail::AsyncWriteOperation; + + template 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(boost::beast::flat_buffer& receive_buffer, boost::beast::flat_buffer& send_buffer, Context& context) + : m_receive_buffer(receive_buffer), m_send_buffer(send_buffer), m_tls_context(context) { + } + + virtual ~StreamCore() = default; + + void tls_emit_data(const uint8_t data[], std::size_t size) override { + m_send_buffer.commit( + boost::asio::buffer_copy(m_send_buffer.prepare(size), boost::asio::buffer(data, size)) + ); + } + + void tls_record_received(uint64_t, const uint8_t data[], std::size_t size) override { + m_receive_buffer.commit( + boost::asio::buffer_copy(m_receive_buffer.prepare(size), boost::asio::const_buffer(data, size)) + ); + } + + void tls_alert(Botan::TLS::Alert alert) override { + m_isAlerted = true; + m_alert = alert; + } + + std::chrono::milliseconds tls_verify_cert_chain_ocsp_timeout() const override { + return std::chrono::milliseconds(1000); + } + + bool tls_session_established(const Botan::TLS::Session& e) override { + if(m_tls_context.m_tls_established_callback){ + m_tls_context.m_tls_established_callback(e); + } + return true; + } + + void tls_verify_cert_chain( + const std::vector& cert_chain, + const std::vector>& ocsp_responses, + const std::vector& trusted_roots, + Usage_Type usage, + const std::string& hostname, + const TLS::Policy& policy) override { + if (m_tls_context.has_verify_callback()) { + m_tls_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); + } + } + + + Botan::TLS::Alert m_alert{}; + bool m_isAlerted = false; + boost::beast::flat_buffer& m_receive_buffer; + boost::beast::flat_buffer& m_send_buffer; + Context& m_tls_context; + }; + + 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 + 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 + typename std::enable_if::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 NotImplemented when side is SERVER - currently only CLIENT is implemented + */ + template + typename std::enable_if::value>::type + setup_native_handle(Connection_Side side, boost::system::error_code& ec) { + if (side == CLIENT) { + m_native_handle = std::unique_ptr( + 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)); + } else { + // TODO: First steps in order to support the server side of this stream would be to instantiate a + // Botan::TLS::Server instance as the stream's native_handle and implement the handshake appropriately. + m_native_handle = std::unique_ptr( + new Server(m_core, + m_context.m_session_manager, + m_context.m_credentials_manager, + m_context.m_policy, + m_context.m_rng)); + } + } + + size_t send_pending_encrypted_data(boost::system::error_code& ec) { + if (ec) { + return 0; + } + + size_t writtenBytes = boost::asio::write(m_nextLayer, send_buffer(), ec); + consume_send_buffer(writtenBytes); + + return writtenBytes; + } + + template + 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 { + native_handle()->send(static_cast(buffer.data()), buffer.size()); + } + 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; + } + } + } + + 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 m_native_handle; + + // Buffer space used to read input intended for the core + std::vector m_input_buffer_space; + const boost::asio::mutable_buffer m_input_buffer; + }; + + } // namespace TLS } // namespace Botan #endif // BOOST_VERSION From 358c1656bbbb2d6a4c59397c6661bb506aa0ca33 Mon Sep 17 00:00:00 2001 From: "jan.capek" Date: Mon, 22 Jul 2019 13:05:44 +0200 Subject: [PATCH 5/5] port parse --- src/lib/utils/http_util/http_util.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/utils/http_util/http_util.cpp b/src/lib/utils/http_util/http_util.cpp index 3fd08b5b823..627252160a2 100644 --- a/src/lib/utils/http_util/http_util.cpp +++ b/src/lib/utils/http_util/http_util.cpp @@ -34,7 +34,11 @@ std::string http_transact(const std::string& hostname, try { - socket = OS::open_socket(hostname, "http", timeout); + std::string delimiter = ":"; + auto it = hostname.find(delimiter); + std::string host = hostname.substr(0, it); + std::string port = hostname.substr(it + 1, hostname.length()); + socket = OS::open_socket(host, port.empty() ? "http" : port, timeout); if(!socket) throw Not_Implemented("No socket support enabled in build"); }