include/boost/corosio/tls_stream.hpp

0.0% Lines (0 / 4) 0.0% Functions (0 / 2)
tls_stream.hpp
f(x) Functions (2)
Line TLA Hits 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/results.hpp>
20 #include <boost/capy/task.hpp>
21
22 #include <cstddef>
23 #include <string_view>
24
25 namespace boost::corosio {
26
27 namespace detail {
28
29 /** A transport a TLS stream owns, held apart from its `any_stream`.
30
31 Destroying the stream cancels the transport's pending operations
32 while the TLS state they complete into stays alive; the transport
33 itself outlives every operation that could be parked on it.
34 */
35 struct tls_owned_transport
36 {
37 /// The owned transport, or null in reference mode.
38 void* p = nullptr;
39
40 /// Destroy the transport `p` points to.
41 void (*destroy)(void*) noexcept = nullptr;
42
43 /// Cancel the transport's pending operations, or null if it can't.
44 void (*cancel)(void*) noexcept = nullptr;
45 };
46
47 /// Destroy an owned transport of type `S`.
48 template<class S>
49 void
50 destroy_owned_transport(void* p) noexcept
51 {
52 delete static_cast<S*>(p);
53 }
54
55 /// Cancel an owned transport of type `S`'s pending operations.
56 template<class S>
57 void
58 cancel_owned_transport(void* p) noexcept
59 {
60 static_cast<S*>(p)->cancel();
61 }
62
63 /// Make the owned-transport record for a transport of type `S`.
64 template<class S>
65 tls_owned_transport
66 make_owned_transport(S* p) noexcept
67 {
68 tls_owned_transport t{p, &destroy_owned_transport<S>, nullptr};
69 if constexpr (requires(S& s) {
70 { s.cancel() } noexcept;
71 })
72 t.cancel = &cancel_owned_transport<S>;
73 return t;
74 }
75
76 } // namespace detail
77
78 /** TLS handshake role.
79
80 Specifies whether to perform the TLS handshake as a client or server.
81
82 @see tls_stream::handshake
83 */
84 enum class tls_role
85 {
86 /// Perform handshake as the connecting client.
87 client,
88
89 /// Perform handshake as the accepting server.
90 server
91 };
92
93 /** Reads, writes, and manages the handshake lifecycle of a TLS
94 session over an underlying stream.
95
96 This class provides a runtime-polymorphic interface for TLS
97 implementations. Derived classes (`openssl_stream`, `wolfssl_stream`)
98 implement the virtual functions to provide backend-specific
99 TLS functionality.
100
101 An @ref io_stream represents OS-level I/O completed by the kernel.
102 TLS streams are coroutine-based instead: their operations are
103 coroutines that orchestrate sub-operations on the underlying stream.
104
105 The non-virtual template wrappers (`read_some`, `write_some`)
106 satisfy the `capy::Stream` concept, enabling TLS streams to
107 be used anywhere a Stream is expected.
108
109 @par Thread Safety
110 Distinct objects: Safe.@n
111 Shared objects: Unsafe, with one exception: one read operation and
112 one write operation may be in flight simultaneously. `shutdown()`
113 may overlap a pending read. On a multi-threaded execution context,
114 all operations on one stream must run within the same
115 `capy::strand`, or must otherwise never run concurrently. A
116 single-threaded context needs no strand.
117
118 @see openssl_stream, wolfssl_stream
119 */
120 class BOOST_COROSIO_DECL tls_stream
121 {
122 public:
123 /// Destroy the TLS stream.
124 virtual ~tls_stream() = default;
125
126 /// Copy construction is disabled; copying a stream would slice the derived session.
127 tls_stream(tls_stream const&) = delete;
128 /// Copy assignment is disabled; copying a stream would slice the derived session.
129 tls_stream& operator=(tls_stream const&) = delete;
130
131 /** Initiate an asynchronous read operation.
132
133 Reads decrypted data into the provided buffer sequence. The
134 operation completes when it reads at least one byte,
135 or an error occurs.
136
137 This non-virtual template wrapper satisfies the `capy::Stream`
138 concept by delegating to the virtual `do_read_some`.
139
140 @par Thread Safety
141 May run concurrently with one operation in the other
142 direction, subject to the class-level threading contract.
143 Two concurrent operations in the same direction are
144 undefined.
145
146 @param buffers The buffer sequence to read data into.
147
148 @return An awaitable yielding `(error_code,std::size_t)`.
149 */
150 template<capy::MutableBufferSequence Buffers>
151 ✗ [[nodiscard]] auto read_some(Buffers const& buffers)
152 {
153 ✗ return do_read_some(buffers);
154 }
155
156 /** Initiate an asynchronous write operation.
157
158 Encrypts and writes data from the provided buffer sequence.
159 The operation completes when it writes at least one byte,
160 or an error occurs.
161
162 This non-virtual template wrapper satisfies the `capy::Stream`
163 concept by delegating to the virtual `do_write_some`.
164
165 @par Thread Safety
166 May run concurrently with one operation in the other
167 direction, subject to the class-level threading contract.
168 Two concurrent operations in the same direction are
169 undefined.
170
171 @param buffers The buffer sequence containing data to write.
172
173 @return An awaitable yielding `(error_code,std::size_t)`.
174 */
175 template<capy::ConstBufferSequence Buffers>
176 ✗ [[nodiscard]] auto write_some(Buffers const& buffers)
177 {
178 ✗ return do_write_some(buffers);
179 }
180
181 /** Asynchronously perform the TLS handshake.
182
183 Initiates the TLS handshake process. For client connections,
184 this sends the ClientHello and processes the server's response.
185 For server connections, this waits for the ClientHello and
186 sends the server's response.
187
188 A handshake attempt consumes the stream state, whether it
189 succeeds or not. A subsequent call behaves as if `reset()` ran
190 first, and performs a fresh handshake using the current
191 configuration.
192
193 @pre The underlying stream must be connected. No other TLS
194 operation may be in progress on this stream.
195
196 @param role The handshake role, client or server.
197
198 @return An awaitable yielding `(error_code)`.
199 */
200 [[nodiscard]] virtual capy::task<capy::results<>> handshake(tls_role role) = 0;
201
202 /** Asynchronously perform a graceful TLS shutdown.
203
204 Initiates the TLS shutdown sequence by sending a close_notify
205 alert and waiting for the peer's close_notify response.
206
207 @pre A handshake must have completed successfully. May overlap
208 a pending read. No concurrent write may be in progress.
209
210 @par Postconditions
211 If the transport ends before the peer's close_notify arrives,
212 the result is `capy::error::stream_truncated`, not success. An
213 unannounced close is indistinguishable from a truncation attack,
214 so it must not be reported as a clean shutdown. A shutdown
215 stopped mid-flight reports canceled. Any other transport
216 error propagates unchanged.
217
218 @return An awaitable yielding `(error_code)`.
219 */
220 [[nodiscard]] virtual capy::task<capy::results<>> shutdown() = 0;
221
222 /** Cancel operations pending on the owned underlying stream.
223
224 Operations parked on the underlying stream complete with
225 `capy::error::canceled`. A stream that owns another TLS stream
226 reaches the innermost transport through this, so destroying
227 the outer stream cancels a read parked through both.
228
229 In reference mode, and when the owned stream has no `cancel()`
230 member, this does nothing; cancel the underlying stream
231 directly instead.
232
233 @par Thread Safety
234 Not thread safe with respect to destroying or moving this
235 stream.
236 */
237 virtual void cancel() noexcept = 0;
238
239 /** Reset TLS session state for reuse.
240
241 Releases TLS session state including session keys and peer
242 certificates, returning the stream to a state where
243 `handshake()` can be called again. Internal memory
244 allocations (I/O buffers) are preserved.
245
246 Calling `handshake()` on a previously-used stream
247 implicitly performs a reset first, so explicit calls
248 are only needed to eagerly release session state.
249
250 @pre No TLS operation (handshake, read, write, shutdown) is
251 in progress.
252
253 @par Thread Safety
254 Not thread safe. The caller must ensure no concurrent
255 operations are in progress on this stream.
256
257 @note If called mid-session before `shutdown()`, pending
258 TLS data is discarded and the peer observes a
259 truncated stream.
260 */
261 virtual void reset() = 0;
262
263 /** Set the peer hostname for SNI and certificate verification.
264
265 Configures the hostname sent in the TLS Server Name
266 Indication extension and matched against the peer
267 certificate during verification. The value takes effect
268 at the next `handshake()`; an established session is not
269 affected. It persists across `reset()`, so a stream reused
270 to reach a different host must set the new name before
271 handshaking again.
272
273 An empty hostname (the default) disables SNI and hostname
274 verification.
275
276 If `hostname` is an IP literal (IPv4 or IPv6), it is matched
277 against the certificate's iPAddress entries instead of its DNS
278 names. No SNI is sent, because RFC 6066 excludes literals.
279 A backend build that cannot match iPAddress entries fails the
280 handshake with `std::errc::function_not_supported` rather
281 than skip verification.
282
283 @par Postconditions
284 The next `handshake()` uses `hostname` for SNI and
285 certificate verification, or neither if it is empty.
286
287 @note The hostname is used for client handshakes only;
288 it is ignored when handshaking as a server.
289
290 @param hostname The peer hostname, or empty to disable.
291 */
292 virtual void set_hostname(std::string_view hostname) = 0;
293
294 /** Return a reference to the underlying stream.
295
296 Provides access to the type-erased underlying stream for
297 operations like cancellation or accessing native handles.
298
299 @warning Do not reseat (assign to) the returned reference.
300 The TLS implementation holds internal state bound to
301 the original stream. Replacing it causes undefined
302 behavior.
303
304 @return Reference to the wrapped stream.
305 */
306 virtual capy::any_stream& next_layer() noexcept = 0;
307
308 /** Return a const reference to the underlying stream.
309
310 @return Const reference to the wrapped stream.
311 */
312 virtual capy::any_stream const& next_layer() const noexcept = 0;
313
314 /** Return the name of the TLS backend.
315
316 @return A string identifying the TLS implementation,
317 such as "openssl" or "wolfssl".
318 */
319 virtual std::string_view name() const noexcept = 0;
320
321 /** Return the ALPN protocol negotiated during the handshake.
322
323 Application-Layer Protocol Negotiation selects a single
324 application protocol (for example `"h2"` or `"http/1.1"`)
325 during the TLS handshake, from the list supplied via
326 @ref tls_context::set_alpn.
327
328 @return The negotiated protocol, or an empty view. It is empty
329 if no protocol was negotiated or ALPN was not offered. It is
330 also empty if the handshake has not completed, or if the build
331 lacks ALPN support.
332
333 @par Thread Safety
334 Safe to call after the handshake completes; not safe to call
335 concurrently with a handshake or reset.
336 */
337 virtual std::string_view alpn_protocol() const noexcept
338 {
339 return {};
340 } // LCOV_EXCL_LINE every concrete stream overrides this; the base default is never called
341
342 protected:
343 /// Default construct; a derived class supplies the session.
344 tls_stream() = default;
345
346 /** Perform the backend-specific decrypted read.
347
348 Derived classes override this to perform TLS decryption
349 and read operations.
350
351 @param buffers Buffer sequence to read into.
352
353 @return An awaitable yielding `(error_code,std::size_t)`.
354 */
355 virtual capy::task<capy::results<std::size_t>> do_read_some(
356 capy::detail::mutable_buffer_array<capy::detail::max_iovec_>
357 buffers) = 0;
358
359 /** Perform the backend-specific encrypted write.
360
361 Derived classes override this to perform TLS encryption
362 and write operations.
363
364 @param buffers Buffer sequence to write from.
365
366 @return An awaitable yielding `(error_code,std::size_t)`.
367 */
368 virtual capy::task<capy::results<std::size_t>> do_write_some(
369 capy::detail::const_buffer_array<capy::detail::max_iovec_> buffers) = 0;
370 };
371
372 } // namespace boost::corosio
373
374 #endif
375