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. Matching happens only when
268 the context's verify mode is not `tls_verify_mode::none`.
269 The value takes effect at the next `handshake()`; an
270 established session is not affected. It persists across `reset()`, so a stream reused
271 to reach a different host must set the new name before
272 handshaking again.
273
274 An empty hostname (the default) disables SNI and hostname
275 verification.
276
277 If `hostname` is an IP literal (IPv4 or IPv6), it is matched
278 against the certificate's iPAddress entries instead of its DNS
279 names. No SNI is sent, because RFC 6066 excludes literals.
280 A backend build that cannot match iPAddress entries fails the
281 handshake with `std::errc::function_not_supported` rather
282 than skip verification.
283
284 @par Postconditions
285 The next `handshake()` uses `hostname` for SNI and
286 certificate verification, or neither if it is empty.
287
288 @note The hostname is used for client handshakes only;
289 it is ignored when handshaking as a server.
290
291 @param hostname The peer hostname, or empty to disable.
292 */
293 virtual void set_hostname(std::string_view hostname) = 0;
294
295 /** Return a reference to the underlying stream.
296
297 Provides access to the type-erased underlying stream for
298 operations like cancellation or accessing native handles.
299
300 @warning Do not reseat (assign to) the returned reference.
301 The TLS implementation holds internal state bound to
302 the original stream. Replacing it causes undefined
303 behavior.
304
305 @return Reference to the wrapped stream.
306 */
307 virtual capy::any_stream& next_layer() noexcept = 0;
308
309 /** Return a const reference to the underlying stream.
310
311 @return Const reference to the wrapped stream.
312 */
313 virtual capy::any_stream const& next_layer() const noexcept = 0;
314
315 /** Return the name of the TLS backend.
316
317 @return A string identifying the TLS implementation,
318 such as "openssl" or "wolfssl".
319 */
320 virtual std::string_view name() const noexcept = 0;
321
322 /** Return the ALPN protocol negotiated during the handshake.
323
324 Application-Layer Protocol Negotiation selects a single
325 application protocol (for example `"h2"` or `"http/1.1"`)
326 during the TLS handshake, from the list supplied via
327 @ref tls_context::set_alpn.
328
329 @return The negotiated protocol, or an empty view. It is empty
330 if no protocol was negotiated or ALPN was not offered. It is
331 also empty if the handshake has not completed, or if the build
332 lacks ALPN support.
333
334 @par Thread Safety
335 Safe to call after the handshake completes; not safe to call
336 concurrently with a handshake or reset.
337 */
338 virtual std::string_view alpn_protocol() const noexcept
339 {
340 return {};
341 } // LCOV_EXCL_LINE every concrete stream overrides this; the base default is never called
342
343 protected:
344 /// Default construct; a derived class supplies the session.
345 tls_stream() = default;
346
347 /** Perform the backend-specific decrypted read.
348
349 Derived classes override this to perform TLS decryption
350 and read operations.
351
352 @param buffers Buffer sequence to read into.
353
354 @return An awaitable yielding `(error_code,std::size_t)`.
355 */
356 virtual capy::task<capy::results<std::size_t>> do_read_some(
357 capy::detail::mutable_buffer_array<capy::detail::max_iovec_>
358 buffers) = 0;
359
360 /** Perform the backend-specific encrypted write.
361
362 Derived classes override this to perform TLS encryption
363 and write operations.
364
365 @param buffers Buffer sequence to write from.
366
367 @return An awaitable yielding `(error_code,std::size_t)`.
368 */
369 virtual capy::task<capy::results<std::size_t>> do_write_some(
370 capy::detail::const_buffer_array<capy::detail::max_iovec_> buffers) = 0;
371 };
372
373 } // namespace boost::corosio
374
375 #endif
376