Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce Example: Minimal HTTPS client #3799

Merged
merged 3 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions doc/api_ref/tls.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1321,24 +1321,37 @@ Code Examples: HTTPS Client using Boost Beast
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Starting with Botan 3.3.0 (and assuming a recent version of Boost), one may use
Botan's TLS using C++20 coroutines. The following example implements a simple
HTTPS client that may be used to fetch content from web servers:
Botan's TLS using C++20 coroutines. The following example implements a minimal
HTTPS client using Botan's default settings to fetch content from web servers.

To establish trust in the server's certificate, Botan attempts to use the
system's trust store (supported on macOS, Linux and Windows). If that does not
work, you might get an error indicating that the certificate is not trusted. In
that case, you can provide a custom trust store by subclassing the
:cpp:class:`Credentials_Manager` and passing it to the :cpp:class:`TLS::Stream`
as shown in :ref:`this example <asio_client_example>`.

Note that Botan's default TLS policy requires servers to provide a valid CRL or
OCSP response for their certificate. To disable this, derive the default policy
class :cpp:class:`TLS::Policy`, override
:cpp:func:`require_cert_revocation_info()` accordingly and pass an object of
your policy via the :cpp:class:`TLS::Context` to the :cpp:class:`TLS::Stream`.

.. literalinclude:: /../src/examples/tls_stream_coroutine_client.cpp
:language: cpp

Of course, the ASIO stream may also be used in a more traditional way, using
callback handler methods instead of coroutines:
.. _asio_client_example:

Aside of the modern coroutines-based approach, the ASIO stream may also be used
in a more traditional way, using callback handler methods instead of coroutines.

Also, this example shows how to use a custom :cpp:class:`Credentials_Manager`
and pass it to the :cpp:class:`TLS::Stream` via a :cpp:class:`TLS::Context`
object.

.. literalinclude:: /../src/examples/tls_stream_client.cpp
:language: cpp

For some websites this might not work and report a "bad_certificate". Botan's
default TLS policy requires that the server sends a valid CRL or OCSP response.
Some servers don't do that and thus the certificate is rejected. To disable this
check, derive the default policy and override `require_cert_revocation_info()`
accordingly.

.. _tls_session_encryption:

TLS Session Encryption
Expand Down
33 changes: 1 addition & 32 deletions src/examples/tls_stream_coroutine_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
#if defined(BOTAN_FOUND_COMPATIBLE_BOOST_ASIO_VERSION) && BOOST_VERSION >= 108100

#include <botan/asio_stream.h>
#include <botan/auto_rng.h>
#include <botan/certstor_system.h>
#include <botan/credentials_manager.h>
#include <botan/tls_session_manager_memory.h>
#include <botan/version.h>

#include <boost/asio/awaitable.hpp>
Expand All @@ -31,33 +27,6 @@ using tcp = boost::asio::ip::tcp;

namespace {

/**
* A simple credentials manager that uses the system's trust store
* to verify certificates.
*/
class System_Credentials_Manager : public Botan::Credentials_Manager {
public:
std::vector<Botan::Certificate_Store*> trusted_certificate_authorities(const std::string&,
const std::string&) override {
return {&m_cert_store};
}

private:
Botan::System_Certificate_Store m_cert_store;
};

/**
* Helper function to set up a Botan TLS Context for the ASIO wrapper.
*/
std::shared_ptr<tls::Context> prepare_context_for(std::string_view server_info) {
auto rng = std::make_shared<Botan::AutoSeeded_RNG>();
return std::make_shared<tls::Context>(std::make_shared<System_Credentials_Manager>(),
rng,
std::make_shared<tls::Session_Manager_In_Memory>(rng),
std::make_shared<tls::Policy>(),
tls::Server_Information(server_info));
}

http::request<http::string_body> create_GET_request(const std::string& host, const std::string& target) {
http::request<http::string_body> req;
req.version(11);
Expand All @@ -77,7 +46,7 @@ static net::awaitable<void> request(std::string host, std::string port, std::str

// Connect to host and establish a TLS session
auto tls_stream =
tls::Stream(prepare_context_for(host),
tls::Stream(tls::Server_Information(host),
net::use_awaitable.as_default_on(beast::tcp_stream(co_await net::this_coro::executor)));
tls_stream.next_layer().expires_after(std::chrono::seconds(30));
co_await tls_stream.next_layer().async_connect(dns_result);
Expand Down
60 changes: 60 additions & 0 deletions src/lib/tls/asio/asio_context.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* TLS Context
* (C) 2024 Jack Lloyd
* 2024 René Meusel, Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/asio_context.h>

#if defined(BOTAN_HAS_HAS_DEFAULT_TLS_CONTEXT)
#include <botan/auto_rng.h>
#include <botan/certstor_system.h>
#include <botan/tls_session_manager_memory.h>
#endif

namespace Botan::TLS {

#if defined(BOTAN_HAS_HAS_DEFAULT_TLS_CONTEXT)

namespace {

/**
* A Credentials_Manager that provides the system's certificate store as trust
* store, if available. Otherwise it defaults to "no trusted certificates".
*/
class Default_Credentials_Manager : public Credentials_Manager {
public:
Default_Credentials_Manager() {
try {
m_cert_store = std::make_unique<System_Certificate_Store>();
} catch(const Not_Implemented&) {
// This platform does not provide an adapter for the system's trust store.
}
}

std::vector<Certificate_Store*> trusted_certificate_authorities(const std::string&, const std::string&) override {
if(m_cert_store) {
return {m_cert_store.get()};
} else {
return {};
}
}

private:
std::unique_ptr<Certificate_Store> m_cert_store;
};

} // namespace

Context::Context(Server_Information server_info) :
m_credentials_manager(std::make_shared<Default_Credentials_Manager>()),
m_rng(std::make_shared<AutoSeeded_RNG>()),
m_session_manager(std::make_shared<Session_Manager_In_Memory>(m_rng)),
m_policy(std::make_shared<Default_Policy>()),
m_server_info(std::move(server_info)) {}

#endif

} // namespace Botan::TLS
13 changes: 13 additions & 0 deletions src/lib/tls/asio/asio_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
#include <botan/tls_server_info.h>
#include <botan/tls_session_manager.h>

#if defined(BOTAN_HAS_AUTO_SEEDING_RNG) && defined(BOTAN_HAS_CERTSTOR_SYSTEM)
#define BOTAN_HAS_HAS_DEFAULT_TLS_CONTEXT
#endif

namespace Botan::TLS {

namespace detail {
Expand All @@ -47,6 +51,15 @@ class Context {
*/
using Verify_Callback = detail::fn_signature_helper<decltype(&Callbacks::tls_verify_cert_chain)>::type;

#if defined(BOTAN_HAS_HAS_DEFAULT_TLS_CONTEXT)
/**
* @brief Construct a TLS stream context with typical defaults
*
* @param server_info Basic information about the host to connect to (SNI)
*/
Context(Server_Information server_info = Server_Information());
#endif

Context(std::shared_ptr<Credentials_Manager> credentials_manager,
std::shared_ptr<RandomNumberGenerator> rng,
std::shared_ptr<Session_Manager> session_manager,
Expand Down
18 changes: 18 additions & 0 deletions src/lib/tls/asio/asio_stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,22 @@ class Stream {
std::shared_ptr<StreamCallbacks> callbacks = std::make_shared<StreamCallbacks>()) :
Stream(std::move(context), std::move(callbacks), std::forward<Arg>(arg)) {}

#if defined(BOTAN_HAS_AUTO_SEEDING_RNG)
/**
* @brief Conveniently construct a new Stream with default settings
*
* This is typically a good starting point for a basic TLS client. For
* much more control and configuration options, consider creating a custom
* Context object and pass it to the respective constructor.
*
* @param server_info Basic information about the host to connect to (SNI)
* @param args Arguments to be forwarded to the construction of the next layer.
*/
template <typename... Args>
explicit Stream(Server_Information server_info, Args&&... args) :
Stream(std::make_shared<Context>(std::move(server_info)), std::forward<Args>(args)...) {}
#endif

virtual ~Stream() = default;

Stream(Stream&& other) = default;
Expand Down Expand Up @@ -946,6 +962,8 @@ class Stream {
// deduction guides for convenient construction from an existing
// underlying transport stream T
template <typename T>
Stream(Server_Information, T) -> Stream<T>;
template <typename T>
Stream(std::shared_ptr<Context>, std::shared_ptr<StreamCallbacks>, T) -> Stream<T>;
template <typename T>
Stream(std::shared_ptr<Context>, T) -> Stream<T>;
Expand Down
Loading