diff options
-rw-r--r-- | doc/api_ref/tls.rst | 40 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_context.h | 93 | ||||
-rw-r--r-- | src/lib/tls/asio/asio_stream.h | 96 | ||||
-rw-r--r-- | src/tests/unit_asio_stream.cpp | 62 |
4 files changed, 210 insertions, 81 deletions
diff --git a/doc/api_ref/tls.rst b/doc/api_ref/tls.rst index 74f8bf79a..f78d59e90 100644 --- a/doc/api_ref/tls.rst +++ b/doc/api_ref/tls.rst @@ -85,7 +85,7 @@ information about the connection. exception which will send a close message to the counterparty and reset the connection state. - .. cpp::function:: void tls_verify_cert_chain(const std::vector<X509_Certificate>& cert_chain, \ + .. cpp:function:: void tls_verify_cert_chain(const std::vector<X509_Certificate>& cert_chain, \ const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_responses, \ const std::vector<Certificate_Store*>& trusted_roots, \ Usage_Type usage, \ @@ -120,7 +120,7 @@ information about the connection. being authenticated using this certificate chain. It can be consulted for values such as allowable signature methods and key sizes. - .. cpp::function:: std::chrono::milliseconds tls_verify_cert_chain_ocsp_timeout() const + .. cpp:function:: std::chrono::milliseconds tls_verify_cert_chain_ocsp_timeout() const Called by default `tls_verify_cert_chain` to set timeout for online OCSP requests on the certificate chain. Return 0 to disable OCSP. Current default is 0. @@ -1618,6 +1618,7 @@ It offers the following interface: Construct a new TLS stream. The *context* parameter will be used to set up the underlying *native handle*, i.e. the :ref:`TLS::Client <tls_client>`, when :cpp:func:`handshake` is called. + Using code must ensure the context is kept alive for the lifetime of the stream. The further *args* will be forwarded to the *next layer*'s constructor. .. cpp:function:: template <typename... Args> \ @@ -1694,22 +1695,23 @@ It offers the following interface: The return type is an automatically deduced specialization of :cpp:class:`boost::asio::async_result`, depending on the *WriteHandler* type. *WriteHandler* should suffice the `requirements to a Boost.Asio write handler <https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/reference/WriteHandler.html>`_. -.. cpp:struct:: TLS::Context +.. cpp:class:: TLS::Context - A helper struct to collect the initialization parameters for the Stream's underlying *native handle* (see :cpp:class:`TLS::Client`). - `TLS::Context` is defined as + A helper class to initialize and configure the Stream's underlying *native handle* (see :cpp:class:`TLS::Client`). - .. code-block:: cpp + .. cpp:function:: Context(Credentials_Manager& credentialsManager, \ + RandomNumberGenerator& randomNumberGenerator, \ + Session_Manager& sessionManager, \ + Policy& policy, \ + Server_Information serverInfo = Server_Information()) - struct Context - { - Credentials_Manager* credentialsManager; - RandomNumberGenerator* randomNumberGenerator; - Session_Manager* sessionManager; - Policy* policy; - Server_Information serverInfo; - }; + Constructor for TLS::Context. + .. cpp:function:: void set_verify_callback(Verify_Callback_T callback) + + Set a user-defined callback function for certificate chain verification. This + will cause the stream to override the default implementation of the + :cpp:func:`tls_verify_cert_chain` callback. Stream Code Example ^^^^^^^^^^^^^^^^^^^^ @@ -1753,11 +1755,11 @@ Stream Code Example boost::asio::ip::tcp::resolver::iterator endpoint_iterator, http::request<http::string_body> req) : request_(req) - , ctx_{&credentials_mgr_, - &rng_, - &session_mgr_, - &policy_, - Botan::TLS::Server_Information()} + , ctx_(credentials_mgr_, + rng_, + session_mgr_, + policy_, + Botan::TLS::Server_Information()) , stream_(io_context, ctx_) { boost::asio::async_connect(stream_.lowest_layer(), endpoint_iterator, diff --git a/src/lib/tls/asio/asio_context.h b/src/lib/tls/asio/asio_context.h index 7de88ebce..e5e99e83a 100644 --- a/src/lib/tls/asio/asio_context.h +++ b/src/lib/tls/asio/asio_context.h @@ -14,8 +14,12 @@ #include <boost/version.hpp> #if BOOST_VERSION >= 106600 +#include <functional> + #include <botan/credentials_manager.h> +#include <botan/ocsp.h> #include <botan/rng.h> +#include <botan/tls_callbacks.h> #include <botan/tls_policy.h> #include <botan/tls_server_info.h> #include <botan/tls_session_manager.h> @@ -23,13 +27,90 @@ namespace Botan { namespace TLS { -struct Context +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 { - Credentials_Manager* credentialsManager; - RandomNumberGenerator* randomNumberGenerator; - Session_Manager* sessionManager; - Policy* policy; - Server_Information serverInfo; + public: + // statically extract the function signature type from Callbacks::tls_verify_cert_chain + // and reuse it as an std::function<> for the verify callback signature + /** + * The signature of the callback function should correspond to the signature of + * Callbacks::tls_verify_cert_chain + */ + using Verify_Callback = + detail::fn_signature_helper<decltype(&Callbacks::tls_verify_cert_chain)>::type; + + Context(Credentials_Manager& credentials_manager, + RandomNumberGenerator& rng, + Session_Manager& session_manager, + Policy& policy, + Server_Information server_info = Server_Information()) : + m_credentials_manager(credentials_manager), + m_rng(rng), + m_session_manager(session_manager), + m_policy(policy), + m_server_info(server_info) + {} + + virtual ~Context() = default; + + Context(Context&&) = default; + Context(const Context&) = delete; + Context& operator=(const Context&) = delete; + Context& operator=(Context&&) = delete; + + /** + * @brief Override the tls_verify_cert_chain callback + * + * This changes the verify_callback in the stream's TLS::Context, and hence the tls_verify_cert_chain callback + * used in the handshake. + * Using this function is equivalent to setting the callback via @see Botan::TLS::Stream::set_verify_callback + * + * @note This function should only be called before initiating the TLS handshake + */ + void set_verify_callback(Verify_Callback callback) + { + m_verify_callback = std::move(callback); + } + + bool has_verify_callback() const + { + return static_cast<bool>(m_verify_callback); + } + + const Verify_Callback& get_verify_callback() const + { + return m_verify_callback; + } + + void set_server_info(const Server_Information& server_info) + { + m_server_info = server_info; + } + + protected: + template <class S, class C> friend class Stream; + + Credentials_Manager& m_credentials_manager; + RandomNumberGenerator& m_rng; + Session_Manager& m_session_manager; + Policy& m_policy; + + Server_Information m_server_info; + Verify_Callback m_verify_callback; }; } // namespace TLS diff --git a/src/lib/tls/asio/asio_stream.h b/src/lib/tls/asio/asio_stream.h index 7cdd8d3dd..e8d9c2930 100644 --- a/src/lib/tls/asio/asio_stream.h +++ b/src/lib/tls/asio/asio_stream.h @@ -52,21 +52,38 @@ class Stream //! \name construction //! @{ + /** + * @brief Construct a new Stream + * + * @param context The context parameter is used to set up the underlying native handle. Using code is + * responsible for lifetime management of the context and must ensure that it is available for the + * lifetime of the stream. + * @param args Arguments to be forwarded to the construction of the next layer. + */ template <typename... Args> explicit Stream(Context& context, Args&& ... args) : m_context(context) , m_nextLayer(std::forward<Args>(args)...) - , m_core(m_receive_buffer, m_send_buffer) + , 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()) {} - // overload for boost::asio::ssl::stream compatibility + /** + * @brief Construct a new Stream + * + * Convenience overload for boost::asio::ssl::stream compatibility. + * + * @param arg This argument is forwarded to the construction of the next layer. + * @param context The context parameter is used to set up the underlying native handle. Using code is + * responsible for lifetime management of the context and must ensure that is available for the + * lifetime of the stream. + */ template <typename Arg> explicit Stream(Arg&& arg, Context& context) : m_context(context) , m_nextLayer(std::forward<Arg>(arg)) - , m_core(m_receive_buffer, m_send_buffer) + , 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()) {} @@ -102,24 +119,29 @@ class Stream //! \name configuration and callback setters //! @{ - //! @throws Not_Implemented - template<typename VerifyCallback> - void set_verify_callback(VerifyCallback callback) + /** + * @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) { - BOTAN_UNUSED(callback); - throw Not_Implemented("set_verify_callback is not implemented"); + m_context.set_verify_callback(std::move(callback)); } /** - * Not Implemented. - * @param ec Will be set to `Botan::ErrorType::NotImplemented` + * @brief Compatibility overload of @ref set_verify_callback + * + * @param ec This parameter is unused. */ - template<typename VerifyCallback> - void set_verify_callback(VerifyCallback callback, - boost::system::error_code& ec) + void set_verify_callback(Context::Verify_Callback callback, boost::system::error_code& ec) { - BOTAN_UNUSED(callback); - ec = Botan::ErrorType::NotImplemented; + BOTAN_UNUSED(ec); + m_context.set_verify_callback(std::move(callback)); } //! @throws Not_Implemented @@ -133,8 +155,7 @@ class Stream * Not Implemented. * @param ec Will be set to `Botan::ErrorType::NotImplemented` */ - void set_verify_depth(int depth, - boost::system::error_code& ec) + void set_verify_depth(int depth, boost::system::error_code& ec) { BOTAN_UNUSED(depth); ec = Botan::ErrorType::NotImplemented; @@ -153,8 +174,7 @@ class Stream * @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) + void set_verify_mode(verify_mode v, boost::system::error_code& ec) { BOTAN_UNUSED(v); ec = Botan::ErrorType::NotImplemented; @@ -511,8 +531,8 @@ class Stream class StreamCore : public Botan::TLS::Callbacks { public: - StreamCore(boost::beast::flat_buffer& receive_buffer, boost::beast::flat_buffer& send_buffer) - : m_receive_buffer(receive_buffer), m_send_buffer(send_buffer) {} + 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; @@ -546,8 +566,27 @@ class Stream return true; } + void tls_verify_cert_chain( + const std::vector<X509_Certificate>& cert_chain, + const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_responses, + const std::vector<Certificate_Store*>& trusted_roots, + Usage_Type usage, + const std::string& hostname, + const TLS::Policy& policy) override + { + if(m_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; } @@ -598,12 +637,13 @@ class Stream { if(side == CLIENT) { - m_native_handle = std::unique_ptr<Client>(new Client(m_core, - *m_context.sessionManager, - *m_context.credentialsManager, - *m_context.policy, - *m_context.randomNumberGenerator, - m_context.serverInfo)); + 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)); } else { @@ -653,7 +693,7 @@ class Stream } } - Context m_context; + Context& m_context; StreamLayer m_nextLayer; boost::beast::flat_buffer m_receive_buffer; diff --git a/src/tests/unit_asio_stream.cpp b/src/tests/unit_asio_stream.cpp index ee80cdba4..5fd67cbd4 100644 --- a/src/tests/unit_asio_stream.cpp +++ b/src/tests/unit_asio_stream.cpp @@ -62,10 +62,6 @@ class MockChannel Botan::TLS::Callbacks& m_callbacks; std::size_t m_bytes_till_complete_record; // number of bytes still to read before tls record is completed bool m_active; - - Botan::TLS::Session_Manager_Noop m_session_manager; - Botan::Null_RNG m_rng; - Botan::TLS::Default_Policy m_policy; }; class ThrowingMockChannel : public MockChannel @@ -130,6 +126,16 @@ class ThrowingAsioStream : public Botan::TLS::Stream<TestStream, ThrowingMockCha */ class Asio_Stream_Tests final : public Test { + Botan::Credentials_Manager m_credentials_manager; + Botan::Null_RNG m_rng; + Botan::TLS::Session_Manager_Noop m_session_manager; + Botan::TLS::Default_Policy m_policy; + + Botan::TLS::Context get_context() + { + return Botan::TLS::Context(m_credentials_manager, m_rng, m_session_manager, m_policy); + } + // use memcmp to check if the data in a is a prefix of the data in b bool contains(const void* a, const void* b, const std::size_t size) { return memcmp(a, b, size) == 0; } @@ -141,7 +147,7 @@ class Asio_Stream_Tests final : public Test void test_sync_handshake(std::vector<Test::Result>& results) { net::io_context ioc; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc, test_data()); ssl.handshake(Botan::TLS::CLIENT); @@ -158,7 +164,7 @@ class Asio_Stream_Tests final : public Test FailCount fc{0, net::error::eof}; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc, fc); ssl.next_layer().connect(remote); @@ -179,7 +185,7 @@ class Asio_Stream_Tests final : public Test net::io_context ioc; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); ThrowingAsioStream ssl(ctx, ioc, test_data()); ssl.next_layer().connect(remote); @@ -197,7 +203,7 @@ class Asio_Stream_Tests final : public Test net::io_context ioc; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc, test_data()); ssl.next_layer().connect(remote); @@ -227,7 +233,7 @@ class Asio_Stream_Tests final : public Test FailCount fc{0, net::error::eof}; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc, fc); ssl.next_layer().connect(remote); @@ -253,7 +259,7 @@ class Asio_Stream_Tests final : public Test net::io_context ioc; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); ThrowingAsioStream ssl(ctx, ioc, test_data()); ssl.next_layer().connect(remote); @@ -275,7 +281,7 @@ class Asio_Stream_Tests final : public Test { net::io_context ioc; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc, test_data()); const std::size_t buf_size = 128; @@ -296,7 +302,7 @@ class Asio_Stream_Tests final : public Test { net::io_context ioc; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc, test_data()); error_code ec; @@ -326,7 +332,7 @@ class Asio_Stream_Tests final : public Test FailCount fc{0, net::error::eof}; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc, fc); ssl.next_layer().connect(remote); @@ -347,7 +353,7 @@ class Asio_Stream_Tests final : public Test net::io_context ioc; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); ThrowingAsioStream ssl(ctx, ioc, test_data()); ssl.next_layer().connect(remote); @@ -367,7 +373,7 @@ class Asio_Stream_Tests final : public Test { net::io_context ioc; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc); const std::size_t buf_size = 128; @@ -390,7 +396,7 @@ class Asio_Stream_Tests final : public Test net::io_context ioc; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc, test_data()); uint8_t data[TEST_DATA_SIZE]; @@ -414,7 +420,7 @@ class Asio_Stream_Tests final : public Test void test_async_read_some_buffer_sequence(std::vector<Test::Result>& results) { net::io_context ioc; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc, test_data()); std::vector<net::mutable_buffer> data; @@ -446,7 +452,7 @@ class Asio_Stream_Tests final : public Test net::io_context ioc; // fail right away FailCount fc{0, net::error::eof}; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc, fc); uint8_t data[TEST_DATA_SIZE]; @@ -469,7 +475,7 @@ class Asio_Stream_Tests final : public Test void test_async_read_some_throw(std::vector<Test::Result>& results) { net::io_context ioc; - Botan::TLS::Context ctx; + auto ctx = get_context(); ThrowingAsioStream ssl(ctx, ioc, test_data()); uint8_t data[TEST_DATA_SIZE]; @@ -494,7 +500,7 @@ class Asio_Stream_Tests final : public Test net::io_context ioc; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc); uint8_t data[TEST_DATA_SIZE]; @@ -521,7 +527,7 @@ class Asio_Stream_Tests final : public Test net::io_context ioc; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc); ssl.next_layer().connect(remote); error_code ec; @@ -541,7 +547,7 @@ class Asio_Stream_Tests final : public Test net::io_context ioc; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc); ssl.next_layer().connect(remote); error_code ec; @@ -581,7 +587,7 @@ class Asio_Stream_Tests final : public Test FailCount fc{0, net::error::eof}; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc, fc); ssl.next_layer().connect(remote); @@ -601,7 +607,7 @@ class Asio_Stream_Tests final : public Test net::io_context ioc; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); ThrowingAsioStream ssl(ctx, ioc); ssl.next_layer().connect(remote); error_code ec; @@ -620,7 +626,7 @@ class Asio_Stream_Tests final : public Test net::io_context ioc; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc); ssl.next_layer().connect(remote); @@ -644,7 +650,7 @@ class Asio_Stream_Tests final : public Test net::io_context ioc; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc); ssl.next_layer().connect(remote); @@ -687,7 +693,7 @@ class Asio_Stream_Tests final : public Test FailCount fc{0, net::error::eof}; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); AsioStream ssl(ctx, ioc, fc); ssl.next_layer().connect(remote); @@ -710,7 +716,7 @@ class Asio_Stream_Tests final : public Test net::io_context ioc; TestStream remote{ioc}; - Botan::TLS::Context ctx; + auto ctx = get_context(); ThrowingAsioStream ssl(ctx, ioc); ssl.next_layer().connect(remote); |