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
reenter
is 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
,reenter
initiates more asynchronous I/O operations on the wrapped stream based on what theSSL
engine needs.If
SSL
wants more bytes, then the operation will initiate an async read. When the read completes,reenter
is called again and the received bytes are written into theBIO
associated with theSSL
.If
SSL
has data to send, then the operation will pull bytes from theBIO
and initiate an asynchronous write of those bytes on the stream.If
SSL
reports an error, thencomplete
will be called with an error code.
If
SSL
does not have any data to read or write, then the operation will consider itself to be complete, and it callscomplete
with 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::context
allows 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
amongoc
but 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::AsyncWriteStream
that wraps anotherasio::AsyncWriteStream
S
.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_base
base class.-
stream(S &&s, asio::ssl::context &ctx)#
Construct a new stream. Perfect-forwards the stream
s
into 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::AsyncReadStream
andasio::AsyncWriteStream
interface for completion callbacks.This allows the
stream
wrapper 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::CompletionToken
and 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.