TLS Encryption
Corosio provides TLS encryption through the tls::context configuration class
and stream wrappers that add encryption to existing connections. This chapter
covers context configuration, stream usage, and common TLS patterns.
| Code snippets assume: |
#include <boost/corosio/tls/context.hpp>
#include <boost/corosio/tls/wolfssl_stream.hpp>
#include <boost/corosio/socket.hpp>
namespace corosio = boost::corosio;
namespace tls = corosio::tls;
Overview
TLS (Transport Layer Security) encrypts data on TCP connections, providing confidentiality, integrity, and authentication. Corosio supports TLS through:
-
tls::context — Portable configuration for certificates, keys, and options
-
tls_stream — Abstract base class adding handshake and shutdown
-
wolfssl_stream — TLS implementation using WolfSSL
-
openssl_stream — TLS implementation using OpenSSL
The typical flow:
// 1. Configure a context
tls::context ctx;
ctx.set_default_verify_paths().value();
ctx.set_verify_mode(tls::verify_mode::peer).value();
ctx.set_hostname("api.example.com");
// 2. Connect a socket
corosio::socket sock(ioc);
sock.open();
(co_await sock.connect(endpoint)).value();
// 3. Wrap in TLS stream
corosio::wolfssl_stream secure(sock, ctx);
(co_await secure.handshake(tls::role::client)).value();
// 4. Use encrypted I/O
auto [ec, n] = co_await secure.read_some(buffer);
tls::context
The tls::context class stores TLS configuration: certificates, keys, trust
anchors, protocol settings, and verification options. Contexts are shared
handles—copies share the same underlying state.
| Don’t modify a context after creating streams from it. The configuration is captured when the first stream is constructed. |
Creating a Context
// Default context (TLS 1.2+ enabled)
tls::context ctx;
The default context has no certificates loaded and doesn’t verify peers. You’ll typically configure it before use.
Credential Loading
For servers (and clients using mutual TLS), load your certificate and private key.
Loading Certificates
// From file
ctx.use_certificate_file("server.crt", tls::file_format::pem).value();
// From memory
std::string cert_data = /* ... */;
ctx.use_certificate(cert_data, tls::file_format::pem).value();
// Certificate chain (cert + intermediates)
ctx.use_certificate_chain_file("fullchain.pem").value();
Loading Private Keys
// From file
ctx.use_private_key_file("server.key", tls::file_format::pem).value();
// From memory
ctx.use_private_key(key_data, tls::file_format::pem).value();
For encrypted private keys, set a password callback first:
ctx.set_password_callback(
[](std::size_t max_len, tls::password_purpose purpose) {
return std::string("my-key-password");
});
ctx.use_private_key_file("encrypted.key", tls::file_format::pem).value();
Trust Anchors
Configure which Certificate Authorities (CAs) to trust for peer verification.
Protocol Configuration
TLS Version
// Require TLS 1.3 minimum
ctx.set_min_protocol_version(tls::version::tls_1_3).value();
// Cap at TLS 1.2 (unusual, but possible)
ctx.set_max_protocol_version(tls::version::tls_1_2).value();
Available versions:
| Version | Description |
|---|---|
|
TLS 1.2 (RFC 5246) |
|
TLS 1.3 (RFC 8446) |
Certificate Verification
Verification Mode
// Don't verify peer (not recommended for clients)
ctx.set_verify_mode(tls::verify_mode::none).value();
// Verify if peer presents certificate
ctx.set_verify_mode(tls::verify_mode::peer).value();
// Require peer certificate (fail if not presented)
ctx.set_verify_mode(tls::verify_mode::require_peer).value();
For HTTPS clients, use peer. For servers requiring client certificates
(mutual TLS), use require_peer.
Hostname Verification (SNI)
For clients, set the expected server hostname:
ctx.set_hostname("api.example.com");
This does two things:
-
Sends Server Name Indication (SNI) so the server knows which certificate to present (important for virtual hosting)
-
Verifies the server certificate matches this hostname
Revocation Checking
Certificate Revocation Lists
// Load CRL from file
ctx.add_crl_file("issuer.crl").value();
// Load CRL from memory
ctx.add_crl(crl_data).value();
OCSP Stapling
For servers, provide a stapled OCSP response:
ctx.set_ocsp_staple(ocsp_response_data).value();
For clients, require the server to staple:
ctx.set_require_ocsp_staple(true);
Revocation Policy
// Don't check revocation (default)
ctx.set_revocation_policy(tls::revocation_policy::disabled);
// Check but allow if status unknown
ctx.set_revocation_policy(tls::revocation_policy::soft_fail);
// Fail if revocation status can't be determined
ctx.set_revocation_policy(tls::revocation_policy::hard_fail);
TLS Streams
TLS streams wrap an underlying io_stream (like socket) to provide encrypted
I/O.
tls_stream Base Class
The tls_stream class inherits from io_stream and adds:
class tls_stream : public io_stream
{
public:
enum handshake_type { client, server };
auto handshake(handshake_type type); // Perform TLS handshake
auto shutdown(); // Graceful TLS shutdown
io_stream& next_layer(); // Access underlying stream
};
Handshake
Before encrypted communication, perform the TLS handshake:
Client Handshake
auto [ec] = co_await secure.handshake(tls_stream::client);
if (ec)
{
std::cerr << "Handshake failed: " << ec.message() << "\n";
co_return;
}
Handshake Errors
Common handshake failures:
| Error | Cause |
|---|---|
Certificate verification failure |
Peer certificate invalid, expired, or untrusted |
Protocol version mismatch |
No common TLS version supported |
Cipher negotiation failure |
No common cipher suite |
Hostname mismatch |
Certificate doesn’t match expected hostname |
Reading and Writing
After handshake, use the stream like any io_stream:
// Read encrypted data
char buf[1024];
auto [ec, n] = co_await secure.read_some(
capy::mutable_buffer(buf, sizeof(buf)));
// Write encrypted data
std::string msg = "Hello, TLS!";
auto [wec, wn] = co_await secure.write_some(
capy::const_buffer(msg.data(), msg.size()));
Shutdown
Graceful TLS shutdown sends a close_notify alert:
auto [ec] = co_await secure.shutdown();
// Then close the underlying socket
sock.close();
Shutdown is optional but recommended. Without it, the peer can’t distinguish between a graceful close and a truncation attack.
Polymorphic Use
Because TLS streams inherit from io_stream, you can write code that works
with both encrypted and unencrypted connections:
capy::task<void> send_request(corosio::io_stream& stream)
{
std::string request = "GET / HTTP/1.1\r\n\r\n";
(co_await corosio::write(
stream, capy::const_buffer(request.data(), request.size()))).value();
std::string response;
co_await corosio::read(stream, response);
}
// Works with plain socket
corosio::socket sock(ioc);
co_await send_request(sock);
// Also works with TLS stream
corosio::wolfssl_stream secure(sock, ctx);
co_await send_request(secure);
HTTPS Client Example
Complete example connecting to an HTTPS server:
capy::task<void> https_get(
corosio::io_context& ioc,
std::string_view hostname,
std::uint16_t port)
{
// Resolve hostname
corosio::resolver resolver(ioc);
auto [resolve_ec, results] = co_await resolver.resolve(
hostname, std::to_string(port));
if (resolve_ec)
throw boost::system::system_error(resolve_ec);
// Connect TCP socket
corosio::socket sock(ioc);
sock.open();
for (auto const& entry : results)
{
auto [ec] = co_await sock.connect(entry.get_endpoint());
if (!ec)
break;
}
// Configure TLS
tls::context ctx;
ctx.set_default_verify_paths().value();
ctx.set_verify_mode(tls::verify_mode::peer).value();
ctx.set_hostname(hostname);
// Wrap in TLS and handshake
corosio::wolfssl_stream secure(sock, ctx);
(co_await secure.handshake(tls_stream::client)).value();
// Send HTTP request
std::string request =
"GET / HTTP/1.1\r\n"
"Host: " + std::string(hostname) + "\r\n"
"Connection: close\r\n"
"\r\n";
(co_await corosio::write(
secure, capy::const_buffer(request.data(), request.size()))).value();
// Read response
std::string response;
auto [ec, n] = co_await corosio::read(secure, response);
// EOF expected when server closes
if (ec && ec != capy::error::eof)
throw boost::system::system_error(ec);
std::cout << response << "\n";
// Graceful shutdown
co_await secure.shutdown();
}
TLS Server Example
Server with certificate and key:
capy::task<void> tls_server(
corosio::io_context& ioc,
std::uint16_t port)
{
// Configure server TLS context
tls::context ctx;
ctx.use_certificate_chain_file("server-fullchain.pem").value();
ctx.use_private_key_file("server.key", tls::file_format::pem).value();
// Set up acceptor
corosio::acceptor acc(ioc);
acc.listen(corosio::endpoint(port));
for (;;)
{
corosio::socket peer(ioc);
auto [ec] = co_await acc.accept(peer);
if (ec) break;
// Spawn handler
capy::run_async(ioc.get_executor())(
handle_tls_client(std::move(peer), ctx));
}
}
capy::task<void> handle_tls_client(
corosio::socket sock,
tls::context ctx)
{
corosio::wolfssl_stream secure(sock, ctx);
auto [ec] = co_await secure.handshake(tls_stream::server);
if (ec)
co_return;
// Handle encrypted connection...
char buf[1024];
auto [read_ec, n] = co_await secure.read_some(
capy::mutable_buffer(buf, sizeof(buf)));
// Graceful shutdown
co_await secure.shutdown();
}
Mutual TLS (mTLS)
For client certificate authentication:
Server Side
tls::context server_ctx;
server_ctx.use_certificate_chain_file("server.pem").value();
server_ctx.use_private_key_file("server.key", tls::file_format::pem).value();
// Require client certificate
server_ctx.set_verify_mode(tls::verify_mode::require_peer).value();
server_ctx.load_verify_file("client-ca.pem").value();
Client Side
tls::context client_ctx;
client_ctx.set_default_verify_paths().value();
client_ctx.set_verify_mode(tls::verify_mode::peer).value();
client_ctx.set_hostname("server.example.com");
// Provide client certificate
client_ctx.use_certificate_file("client.crt", tls::file_format::pem).value();
client_ctx.use_private_key_file("client.key", tls::file_format::pem).value();
Thread Safety
| Operation | Thread Safety |
|---|---|
Distinct contexts |
Safe from different threads |
Shared context (read-only) |
Safe after configuration complete |
Distinct streams |
Safe from different threads |
Same stream |
NOT safe for concurrent operations |
Don’t perform concurrent read, write, or handshake operations on the same TLS stream.
Next Steps
-
Sockets — The underlying stream
-
Composed Operations — read() and write()
-
TLS Context Tutorial — Step-by-step configuration