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/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 ✗ [[nodiscard]] auto read_some(Buffers const& buffers)
100 {
101 ✗ 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 ✗ [[nodiscard]] auto write_some(Buffers const& buffers)
125 {
126 ✗ 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
306