TLS Streams#
Warning
This page is for amongoc developers and the documented components and
behavior are not part of any public API guarantees.
Background#
In amongoc, the TLS engine is not tied to a socket. Instead, OpenSSL provides
a state machine SSL interface using a BIO as an abstraction around an
opaque I/O object. It doesn’t concern itself with how bytes move in/out of that
BIO, it just expects it to behave like a stream of bytes.
Any I/O operation on the high-level TLS stream can be composed of an arbitrary
number of reads and writes to the actual underlying stream. As such, it is not
as simple as reading/writing data into the SSL and then receiving some
plaintext/cyphertext. A single read or write of plaintext may invoke more reads
or writes on the ciphertext stream, so the amongoc wrapper’s read/write
operations need to be able to handle such a situation.
The stream wrapper in amongoc provides an asynchronous interface around that
BIO by moving bytes to/from the BIO from/to a wrapped asio::AsyncWriteStream.
Operation Implementation#
- <amongoc/tls/stream.hpp> (header file)#
- <amongoc/tls/detail/stream_base.hpp> (header file)#
(Private headers)
Provides the implementation of a TLS stream wrapper. See:
amongoc::tls::stream
All operations are implemented with an abstract base class
amongoc::tls::detail::stream_base::operation_base, which provides a basic
interface:
reenter(ec)is called to pump the OpenSSL state machine. The error code given comes from the operation that reentered it. On initial reentry, this error code is always zero.complete(ec)will be called exactly once for an operation and may destroy the operation object. An outstanding operation must complete before a new operation may be enqueued.If the error code given to
reenteris non-zero, then the operation immediately callscomplete()with that error.do_ssl_operation()actually invokes the OpenSSL API for the operation. It may be called multiple times. It should return an integer for the result of the operation. It is called byreenter().After
do_ssl_operation,reenterinitiates more asynchronous I/O operations on the wrapped stream based on what theSSLengine needs.If
SSLwants more bytes, then the operation will initiate an async read. When the read completes,reenteris called again and the received bytes are written into theBIOassociated with theSSL.If
SSLhas data to send, then the operation will pull bytes from theBIOand initiate an asynchronous write of those bytes on the stream.If
SSLreports an error, thencompletewill be called with an error code.
If
SSLdoes not have any data to read or write, then the operation will consider itself to be complete, and it callscompletewith a success status.
Certificate Verification#
The certificate verification implementation in amongoc is based on the Asio
interface. There are two ways to verify certificates:
Verification associated with the context:
asio::ssl::contextallows a verification callback to be attached to the context, and this will apply to all derived SSL engines. This relies entirely on the Asio implementation.Verification can be associated with a particular stream wrapper. This is implemented in
amongocbut follows the same implementation pattern that Asio uses for stream-associated verification.
In either case, a verification callback is tied to an object using
set_verify_callback. Refer to
the Asio documentation for information on how the
callback is invoked.
Internal API#
-
template<typename S>
class [[internal]] amongoc::tls::stream# Implements an Asio
asio::AsyncWriteStreamthat wraps anotherasio::AsyncWriteStreamS.We cannot reuse the
asio::ssl::stream<>, because it requires access to a proper Asio execution context that we cannot provide with our event loop.Many of the APIs below are provided by the
stream_basebase class.-
stream(S &&s, asio::ssl::context &ctx)#
Construct a new stream. Perfect-forwards the stream
sinto place.
-
::SSL *ssl_cptr() const#
Obtain the pointer to the native OpenSSL engine object. This can be used to invoke OpenSSL APIs that are not otherwise exposed on the stream.
-
std::error_code set_verify_mode(asio::ssl::verify_mode v)#
-
std::error_code set_verify_callback(auto fn)#
Set the verification mode and verification callback.
-
std::error_code set_server_name(const std::string &sn)#
Set the server name for the peer. This is required if the peer uses SNI.
-
nanosender_of<result<mlib::unit, std::error_code>> auto connect()#
Obtian a nanosender that performs a client-side handshake on the stream.
-
void async_write_some(auto &&cbufs, auto &&callback)#
-
void async_read_some(auto &&mbufs, auto &&callback)#
Implement the
asio::AsyncReadStreamandasio::AsyncWriteStreaminterface for completion callbacks.This allows the
streamwrapper to be used in higher-level interfaces.Note
Don’t use these directly. Instead, use the Asio algorithms, which will allow you to use a proper
asio::CompletionTokenand have stronger behavioral guarantees.
-
stream(S &&s, asio::ssl::context &ctx)#
-
struct SSL#
An OpenSSL TLS stream object. Refer to the OpenSSL documentation
-
class asio::ssl::context#
We reuse Asio’s wrapper around an OpenSSL context, as it implements a lot of useful functionality for us, and does not require access to a full Asio event loop.