cpp: c++ networking https silice
Created on Oct 10, 2024
Updated On Oct 13, 2024 - Add asio::as_tuple, update throwing c++ std::exception.
Reading this article requires reading c++ asio beast http networking
Botan http tls (https) client commuication add a handshake to the boost::asio communication after connection.
tls_stream.async_handshake(...) starts an asynchronous ssl (tls) handshake.
tls_stream.async_handshake( // async handshake Botan::TLS::Connection_Side::Client, [self = this->shared_from_this()] (std::error_code ec) // handler { } );
tls_stream is an object created from Botan::TLS::Stream.
Botan::TLS::Stream<beast::tcp_stream> tls_stream{tls_context, io_context};
Botan::TLS::Stream is a compatible tls stream to boost::asio.
Stream-Layer-Type: beast::tcp_stream - the next layer type.
io_context is an object of asio::io_context.
tls_context is an object created from Botan::TLS::Context.
Botan::TLS::Context tls_context_object{credentials_manager, rng, session_manager, policy, server_info);
std::shared_ptr<Botan::TLS::Context> tls_context { std::make_shared<Botan::TLS::Context>( credentials_manager, rng, session_manager, policy, server_info ) };
Botan::TLS::Context is a helper class to initialize and configure Botan::TLS::Stream.
Arguments:
Tip | |
---|---|
QuoteU* shall be implicitly convertible to T* Constructor: https://cplusplus.com/reference/memory/shared_ptr/shared_ptr operator=: https://cplusplus.com/reference/memory/shared_ptr/operator= |
std::shared_ptr<T> ptr = std::make_shared<U>(...);
std::shared_ptr<T> ptr; ptr = std::make_shared<U>(...);
In this article, U is the derived class, T is the base class, so U * is convertibale to T * .
std::shared_ptr<Botan::RandomNumberGenerator> rng { std::make_shared<Botan::AutoSeeded_RNG>() };
Botan::RandomNumberGenerator is an interface to a crypto random number generator. It is an abstract class.
Some implementors of Botan::RandomNumberGenerator:
std::shared_ptr<Botan::Credentials_Manager> credentials_manager { std::make_shared<my_tls::credentials_manager>() };
Botan::Credentials_Manger - Interface for a credentials manager.
my_tls::credentials_manager - my_tls::credentials_manager is a user-defined class, which must be derived from Botan::Credentials_Manager and specify how to get the system certificate store. Otherwise the tls handshake will report certificate error.
std::shared_ptr<Botan::TLS::Session_Manager> session_manager { std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng) };
Botan::TLS::Session_Manager is an interface to systems which can save session parameters for supporting session resumption. - it is an abstract class.
Other session manager:
The std::shared_ptr smart pointer of rng described before.
std::shared_ptr<Botan::TLS::Policy> policy { std::make_shared<Botan::TLS::Policy>() };
Botan::TLS::Policy is the TLS policy base class. Inherit it and overload as desired to suit local policy concerns.
Botan::TLS::Server_Information server_info;
Botan::TLS::Server_Information represents information known about a TLS server.
All of the above parameters are for creating tls_stream, the object of Botan::TLS::Stream<beast::tcp_stream>. After getting object tls_stream, we can let encrypted boost::asio/boost::beast networking work.
c++ example
#include <boost/beast.hpp> #include <boost/asio.hpp> #include <botan/tls.h> #include <botan/asio_stream.h> #include <botan/certstor_system.h> #include <botan/auto_rng.h> #include <iostream> using std::string_literals::operator""s; namespace beast = boost::beast; namespace asio = boost::asio; namespace http = beast::http; using asio::ip::tcp; namespace iotls { class credentials_manager: virtual public Botan::Credentials_Manager { protected: using ret_t = std::vector<Botan::Certificate_Store *>; protected: Botan::System_Certificate_Store __store; public: ret_t trusted_certificate_authorities(const std::string & type, const std::string & context) override { return ret_t{&__store}; } public: virtual ~credentials_manager() {} }; class https_client: virtual public std::enable_shared_from_this< iotls::https_client > { protected: asio::io_context & __io_context; const std::string __host; const std::string __port; protected: tcp::resolver __resolver; http::request<http::string_body> __request; http::response<http::string_body> __response; beast::flat_buffer __buffer; protected: // Parameters for __tls_context std::shared_ptr<Botan::RandomNumberGenerator> __rng; std::shared_ptr<Botan::Credentials_Manager> __cred_man; std::shared_ptr<Botan::TLS::Session_Manager> __sess_man; std::shared_ptr<Botan::TLS::Policy> __policy; Botan::TLS::Server_Information __server_info; // __tls_context std::shared_ptr<Botan::TLS::Context> __tls_context; // __tls_stream Botan::TLS::Stream<beast::tcp_stream> __tls_stream; public: https_client( asio::io_context & io_context__, const std::string_view host__, const std::string_view port__ ): __io_context{io_context__}, __host{host__}, __port{port__}, __resolver{__io_context}, // Construct parameters for __tls_context __rng{std::make_shared<Botan::AutoSeeded_RNG>()}, __cred_man{std::make_shared<iotls::credentials_manager>()}, __sess_man{std::make_shared<Botan::TLS::Session_Manager_In_Memory>(__rng)}, __policy{std::make_shared<Botan::TLS::Policy>()}, __server_info{}, // __tls_context __tls_context{ std::make_shared<Botan::TLS::Context>( __cred_man, __rng, __sess_man, __policy, __server_info ) }, // __tls_stream __tls_stream{__tls_context, __io_context} { std::clog << "https session is created.\n"; } public: virtual ~https_client() { __tls_stream.async_shutdown( [] (std::error_code ec) { std::clog << "tls async shutdown.\n"; if (ec) std::clog << "0\n"; else std::clog << "1\n"; } ); std::clog << "https session is ended.\n"; } public: void run() { this->start(); } protected: void start() { this->resolve(); } protected: void resolve() { __resolver.async_resolve( __host, __port, [self=this->shared_from_this()] ( std::error_code ec, tcp::resolver::results_type results ) { if (ec) throw std::system_error{ec, "async resolve error."}; std::clog << "async resolve OK.\n"; self->connect(std::move(results)); } ); } void connect(tcp::resolver::results_type && results) { __tls_stream.next_layer().expires_after(std::chrono::seconds(3)); __tls_stream.next_layer().async_connect( results, [self=this->shared_from_this()] ( std::error_code ec, tcp::endpoint ep ) { if (ec) throw std::system_error{ec, "async connect error."}; std::clog << "async connect OK.\n"; self->handshake(); } ); } void handshake() { __tls_stream.next_layer().expires_after(std::chrono::seconds(3)); __tls_stream.async_handshake( Botan::TLS::Connection_Side::Client, [self = this->shared_from_this()] ( std::error_code ec ) { if (ec) throw std::system_error{ec, "tls async handshake error."}; std::clog << "tls async handshake OK.\n"; self->write(); } ); } protected: void write() { __request.target("/"); __request.set(http::field::host, __host); __request.method(http::verb::get); __request.version(11); __tls_stream.next_layer().expires_after(std::chrono::seconds(3)); http::async_write( __tls_stream, __request, [self = this->shared_from_this()] ( std::error_code ec, unsigned long bytes ) { if (ec) throw std::system_error{ec, "async write error."}; std::clog << "async write OK." << std::endl; self->read(); } ); } void read() { http::async_read( __tls_stream, __buffer, __response, [self = this->shared_from_this()] ( std::error_code ec, unsigned long bytes ) { if (ec) throw std::system_error{ec, "async read error."}; std::cout << "Got:\n\n"; std::cout << self->__response << "\n\n"; } ); } }; // class https_client } // namespace iotls int main(int argc, char * argv[]) try { if (argc != 3) throw std::runtime_error{""s + argv[0] + " <remote host> <remote port>"}; asio::io_context io_context; std::make_shared<iotls::https_client>(io_context, argv[1], argv[2])->run(); io_context.run(); } catch (const std::exception & e) { std::cerr << "ERROR:\n" << e.what() << std::endl; }
c++ example
Coroutines (co_await ...) can not be used in a destructor.
#include <boost/beast.hpp> #include <boost/asio.hpp> #include <boost/asio/co_spawn.hpp> #include <botan/certstor_system.h> #include <botan/tls.h> #include <botan/auto_rng.h> #include <iostream> #include <botan/asio_stream.h> namespace asio = boost::asio; namespace beast = boost::beast; namespace http = beast::http; using asio::ip::tcp; using std::string_literals::operator""s; namespace my::https { class credentials_manager: virtual public Botan::Credentials_Manager // Requires to load certs from system, otherwise the handshaking reports cert error. { protected: Botan::System_Certificate_Store __store; public: std::vector<Botan::Certificate_Store *> trusted_certificate_authorities(const std::string & type, const std::string & context) override { return {&__store}; } }; class client: virtual public std::enable_shared_from_this<my::https::client> { protected: const std::string __host; const std::string __port; tcp::resolver __resolver; http::request<http::string_body> __request; http::response<http::string_body> __response; beast::flat_buffer __buffer; protected: // params for __tls_context std::shared_ptr<Botan::RandomNumberGenerator> __rng; std::shared_ptr<Botan::Credentials_Manager> __cred_man; std::shared_ptr<Botan::TLS::Session_Manager> __sess_man; std::shared_ptr<Botan::TLS::Policy> __policy; Botan::TLS::Server_Information __server_info; // __tls_context std::shared_ptr<Botan::TLS::Context> __tls_context; // __tls_stream Botan::TLS::Stream<beast::tcp_stream> __tls_stream; public: virtual ~client() { std::clog << "https session is closed.\n"; } public: client( asio::any_io_executor executor__, const std::string_view host__, const std::string_view port__ ): __host{host__}, __port{port__}, __resolver{executor__}, // params for __tls_context __rng{std::make_shared<Botan::AutoSeeded_RNG>()}, __cred_man{std::make_shared<my::https::credentials_manager>()}, __sess_man{std::make_shared<Botan::TLS::Session_Manager_In_Memory>(__rng)}, __policy{std::make_shared<Botan::TLS::Policy>()}, __server_info{}, // __tls_context __tls_context{ std::make_shared<Botan::TLS::Context>( __cred_man, __rng, __sess_man, __policy, __server_info ) }, // __tls_stream __tls_stream{__tls_context, executor__} { std::clog << "https session is created.\n"; } public: asio::awaitable<void> run() { co_await this->start(); } protected: asio::awaitable<void> start() { co_await this->resolve(); } protected: asio::awaitable<void> resolve() { auto [ec, results] = co_await __resolver.async_resolve( __host, __port, asio::as_tuple(asio::use_awaitable) ); if (ec) throw std::system_error{ec, "async resolve error!"}; std::clog << "async resolve OK!" << std::endl; co_await this->connect(std::move(results)); } asio::awaitable<void> connect(tcp::resolver::results_type && results__) { __tls_stream.next_layer().expires_after(std::chrono::seconds(3)); // Only wait 3 seconds. auto [ec, ep] = co_await __tls_stream.next_layer().async_connect( results__, asio::as_tuple(asio::use_awaitable) ); if (ec) throw std::system_error{ec, "async connect error!"}; std::clog << "async connect OK!" << std::endl; co_await this->handshake(); } asio::awaitable<void> handshake() { __tls_stream.next_layer().expires_after(std::chrono::seconds(3)); auto [ec] = co_await __tls_stream.async_handshake( Botan::TLS::Connection_Side::Client, asio::as_tuple(asio::use_awaitable) ); if (ec) throw std::system_error{ec, "async handshake error!"}; std::clog << "async handshake OK!\n"; co_await this->write(); } protected: asio::awaitable<void> write() { __request.method(http::verb::get); __request.target("/"); __request.set(http::field::host, __host); __request.version(11); __tls_stream.next_layer().expires_after(std::chrono::seconds(3)); auto [ec, bytes] = co_await http::async_write( __tls_stream, __request, asio::as_tuple(asio::use_awaitable) ); if (ec) throw std::system_error{ec, "async write request error."}; std::clog << "async write request OK." << std::endl; co_await this->read(); } asio::awaitable<void> read() { __tls_stream.next_layer().expires_after(std::chrono::seconds(3)); auto [ec, bytes] = co_await http::async_read( __tls_stream, __buffer, __response, asio::as_tuple(asio::use_awaitable) ); if (ec) throw std::system_error{ec, "async read error!"}; std::cout << __response.body().data() << std::endl; co_await this->close(); } protected: asio::awaitable<void> close() { __tls_stream.next_layer().expires_after(std::chrono::seconds(3)); auto [ec] = co_await __tls_stream.async_shutdown(asio::as_tuple(asio::use_awaitable)); if (ec) std::clog << "0\n"; else std::clog << "1\n"; } }; // class client } // namespace my::https int main(int argc, char * argv[]) try { if (argc != 3) throw std::runtime_error{""s + argv[0] + " <remote host> <remote port>"}; auto io_context = asio::io_context{}; asio::co_spawn( io_context, [host=argv[1], port=argv[2]] -> asio::awaitable<void> { try { auto executor = co_await asio::this_coro::executor; co_await std::make_shared<my::https::client>(executor, host, port)->run(); } catch (const std::exception & e) { std::cerr << "Run ERROR:\n" << e.what() << std::endl; } }, asio::detached ); io_context.run(); } catch (const std::exception & e) { std::cerr << "ERROR:\n" << e.what() << std::endl; }
c++ std::exception:
std::cout.write(err.data(), err.size());
std::cout << std::endl;
caught:
================================================== # The c++ programming language. # # # # Home: cppfx.xyz # # Join c++ Discord: yZcauUAUyC # # Deck # ==================================================