TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Michael Vandeberg
4 : // Copyright (c) 2026 Steve Gerbino
5 : //
6 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
7 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 : //
9 : // Official repository: https://github.com/cppalliance/corosio
10 : //
11 :
12 : #ifndef BOOST_COROSIO_TLS_STREAM_HPP
13 : #define BOOST_COROSIO_TLS_STREAM_HPP
14 :
15 : #include <boost/corosio/detail/config.hpp>
16 : #include <boost/capy/buffers.hpp>
17 : #include <boost/capy/detail/buffer_array.hpp>
18 : #include <boost/capy/io/any_stream.hpp>
19 : #include <boost/capy/io_task.hpp>
20 :
21 : #include <cstddef>
22 : #include <string_view>
23 :
24 : namespace boost::corosio {
25 :
26 : /** TLS handshake role.
27 :
28 : Specifies whether to perform the TLS handshake as a client or server.
29 :
30 : @see tls_stream::handshake
31 : */
32 : enum class tls_role
33 : {
34 : /// Perform handshake as the connecting client.
35 : client,
36 :
37 : /// Perform handshake as the accepting server.
38 : server
39 : };
40 :
41 : /** Reads, writes, and manages the handshake lifecycle of a TLS
42 : session over an underlying stream.
43 :
44 : This class provides a runtime-polymorphic interface for TLS
45 : implementations. Derived classes (`openssl_stream`, `wolfssl_stream`)
46 : implement the virtual functions to provide backend-specific
47 : TLS functionality.
48 :
49 : An @ref io_stream represents OS-level I/O completed by the kernel.
50 : TLS streams are coroutine-based instead: their operations are
51 : coroutines that orchestrate sub-operations on the underlying stream.
52 :
53 : The non-virtual template wrappers (`read_some`, `write_some`)
54 : satisfy the `capy::Stream` concept, enabling TLS streams to
55 : be used anywhere a Stream is expected.
56 :
57 : @par Thread Safety
58 : Distinct objects: Safe.@n
59 : Shared objects: Unsafe, with one exception: one read operation and
60 : one write operation may be in flight simultaneously. `shutdown()`
61 : may overlap a pending read. On a multi-threaded execution context,
62 : all operations on one stream must run within the same
63 : `capy::strand`, or must otherwise never run concurrently. A
64 : single-threaded context needs no strand.
65 :
66 : @see openssl_stream, wolfssl_stream
67 : */
68 : class BOOST_COROSIO_DECL tls_stream
69 : {
70 : public:
71 : /// Destroy the TLS stream.
72 : virtual ~tls_stream() = default;
73 :
74 : /// Copy construction is disabled; copying a stream would slice the derived session.
75 : tls_stream(tls_stream const&) = delete;
76 : /// Copy assignment is disabled; copying a stream would slice the derived session.
77 : tls_stream& operator=(tls_stream const&) = delete;
78 :
79 : /** Initiate an asynchronous read operation.
80 :
81 : Reads decrypted data into the provided buffer sequence. The
82 : operation completes when it reads at least one byte,
83 : or an error occurs.
84 :
85 : This non-virtual template wrapper satisfies the `capy::Stream`
86 : concept by delegating to the virtual `do_read_some`.
87 :
88 : @par Thread Safety
89 : May run concurrently with one operation in the other
90 : direction, subject to the class-level threading contract.
91 : Two concurrent operations in the same direction are
92 : undefined.
93 :
94 : @param buffers The buffer sequence to read data into.
95 :
96 : @return An awaitable yielding `(error_code,std::size_t)`.
97 : */
98 : template<capy::MutableBufferSequence Buffers>
99 MIS 0 : [[nodiscard]] auto read_some(Buffers const& buffers)
100 : {
101 0 : return do_read_some(buffers);
102 : }
103 :
104 : /** Initiate an asynchronous write operation.
105 :
106 : Encrypts and writes data from the provided buffer sequence.
107 : The operation completes when it writes at least one byte,
108 : or an error occurs.
109 :
110 : This non-virtual template wrapper satisfies the `capy::Stream`
111 : concept by delegating to the virtual `do_write_some`.
112 :
113 : @par Thread Safety
114 : May run concurrently with one operation in the other
115 : direction, subject to the class-level threading contract.
116 : Two concurrent operations in the same direction are
117 : undefined.
118 :
119 : @param buffers The buffer sequence containing data to write.
120 :
121 : @return An awaitable yielding `(error_code,std::size_t)`.
122 : */
123 : template<capy::ConstBufferSequence Buffers>
124 0 : [[nodiscard]] auto write_some(Buffers const& buffers)
125 : {
126 0 : return do_write_some(buffers);
127 : }
128 :
129 : /** Asynchronously perform the TLS handshake.
130 :
131 : Initiates the TLS handshake process. For client connections,
132 : this sends the ClientHello and processes the server's response.
133 : For server connections, this waits for the ClientHello and
134 : sends the server's response.
135 :
136 : A handshake attempt consumes the stream state, whether it
137 : succeeds or not. A subsequent call behaves as if `reset()` ran
138 : first, and performs a fresh handshake using the current
139 : configuration.
140 :
141 : @pre The underlying stream must be connected. No other TLS
142 : operation may be in progress on this stream.
143 :
144 : @param role The handshake role, client or server.
145 :
146 : @return An awaitable yielding `(error_code)`.
147 : */
148 : [[nodiscard]] virtual capy::io_task<> handshake(tls_role role) = 0;
149 :
150 : /** Asynchronously perform a graceful TLS shutdown.
151 :
152 : Initiates the TLS shutdown sequence by sending a close_notify
153 : alert and waiting for the peer's close_notify response.
154 :
155 : @pre A handshake must have completed successfully. May overlap
156 : a pending read. No concurrent write may be in progress.
157 :
158 : @par Postconditions
159 : If the transport ends before the peer's close_notify arrives,
160 : the result is `capy::error::stream_truncated`, not success. An
161 : unannounced close is indistinguishable from a truncation attack,
162 : so it must not be reported as a clean shutdown. A shutdown
163 : stopped mid-flight reports canceled. Any other transport
164 : error propagates unchanged.
165 :
166 : @return An awaitable yielding `(error_code)`.
167 : */
168 : [[nodiscard]] virtual capy::io_task<> shutdown() = 0;
169 :
170 : /** Reset TLS session state for reuse.
171 :
172 : Releases TLS session state including session keys and peer
173 : certificates, returning the stream to a state where
174 : `handshake()` can be called again. Internal memory
175 : allocations (I/O buffers) are preserved.
176 :
177 : Calling `handshake()` on a previously-used stream
178 : implicitly performs a reset first, so explicit calls
179 : are only needed to eagerly release session state.
180 :
181 : @pre No TLS operation (handshake, read, write, shutdown) is
182 : in progress.
183 :
184 : @par Thread Safety
185 : Not thread safe. The caller must ensure no concurrent
186 : operations are in progress on this stream.
187 :
188 : @note If called mid-session before `shutdown()`, pending
189 : TLS data is discarded and the peer observes a
190 : truncated stream.
191 : */
192 : virtual void reset() = 0;
193 :
194 : /** Set the peer hostname for SNI and certificate verification.
195 :
196 : Configures the hostname sent in the TLS Server Name
197 : Indication extension and matched against the peer
198 : certificate during verification. The value takes effect
199 : at the next `handshake()`; an established session is not
200 : affected. It persists across `reset()`, so a stream reused
201 : to reach a different host must set the new name before
202 : handshaking again.
203 :
204 : An empty hostname (the default) disables SNI and hostname
205 : verification.
206 :
207 : If `hostname` is an IP literal (IPv4 or IPv6), it is matched
208 : against the certificate's iPAddress entries instead of its DNS
209 : names. No SNI is sent, because RFC 6066 excludes literals.
210 : A backend build that cannot match iPAddress entries fails the
211 : handshake with `std::errc::function_not_supported` rather
212 : than skip verification.
213 :
214 : @par Postconditions
215 : The next `handshake()` uses `hostname` for SNI and
216 : certificate verification, or neither if it is empty.
217 :
218 : @note The hostname is used for client handshakes only;
219 : it is ignored when handshaking as a server.
220 :
221 : @param hostname The peer hostname, or empty to disable.
222 : */
223 : virtual void set_hostname(std::string_view hostname) = 0;
224 :
225 : /** Return a reference to the underlying stream.
226 :
227 : Provides access to the type-erased underlying stream for
228 : operations like cancellation or accessing native handles.
229 :
230 : @warning Do not reseat (assign to) the returned reference.
231 : The TLS implementation holds internal state bound to
232 : the original stream. Replacing it causes undefined
233 : behavior.
234 :
235 : @return Reference to the wrapped stream.
236 : */
237 : virtual capy::any_stream& next_layer() noexcept = 0;
238 :
239 : /** Return a const reference to the underlying stream.
240 :
241 : @return Const reference to the wrapped stream.
242 : */
243 : virtual capy::any_stream const& next_layer() const noexcept = 0;
244 :
245 : /** Return the name of the TLS backend.
246 :
247 : @return A string identifying the TLS implementation,
248 : such as "openssl" or "wolfssl".
249 : */
250 : virtual std::string_view name() const noexcept = 0;
251 :
252 : /** Return the ALPN protocol negotiated during the handshake.
253 :
254 : Application-Layer Protocol Negotiation selects a single
255 : application protocol (for example `"h2"` or `"http/1.1"`)
256 : during the TLS handshake, from the list supplied via
257 : @ref tls_context::set_alpn.
258 :
259 : @return The negotiated protocol, or an empty view. It is empty
260 : if no protocol was negotiated or ALPN was not offered. It is
261 : also empty if the handshake has not completed, or if the build
262 : lacks ALPN support.
263 :
264 : @par Thread Safety
265 : Safe to call after the handshake completes; not safe to call
266 : concurrently with a handshake or reset.
267 : */
268 : virtual std::string_view alpn_protocol() const noexcept
269 : {
270 : return {};
271 : } // LCOV_EXCL_LINE every concrete stream overrides this; the base default is never called
272 :
273 : protected:
274 : /// Default construct; a derived class supplies the session.
275 : tls_stream() = default;
276 :
277 : /** Perform the backend-specific decrypted read.
278 :
279 : Derived classes override this to perform TLS decryption
280 : and read operations.
281 :
282 : @param buffers Buffer sequence to read into.
283 :
284 : @return An awaitable yielding `(error_code,std::size_t)`.
285 : */
286 : virtual capy::io_task<std::size_t> do_read_some(
287 : capy::detail::mutable_buffer_array<capy::detail::max_iovec_>
288 : buffers) = 0;
289 :
290 : /** Perform the backend-specific encrypted write.
291 :
292 : Derived classes override this to perform TLS encryption
293 : and write operations.
294 :
295 : @param buffers Buffer sequence to write from.
296 :
297 : @return An awaitable yielding `(error_code,std::size_t)`.
298 : */
299 : virtual capy::io_task<std::size_t> do_write_some(
300 : capy::detail::const_buffer_array<capy::detail::max_iovec_> buffers) = 0;
301 : };
302 :
303 : } // namespace boost::corosio
304 :
305 : #endif
|