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. Matching happens only when
199 the context's verify mode is not `tls_verify_mode::none`.
200 The value takes effect at the next `handshake()`; an
201 established session is not affected. It persists across `reset()`, so a stream reused
202 to reach a different host must set the new name before
203 handshaking again.
204
205 An empty hostname (the default) disables SNI and hostname
206 verification.
207
208 If `hostname` is an IP literal (IPv4 or IPv6), it is matched
209 against the certificate's iPAddress entries instead of its DNS
210 names. No SNI is sent, because RFC 6066 excludes literals.
211 A backend build that cannot match iPAddress entries fails the
212 handshake with `std::errc::function_not_supported` rather
213 than skip verification.
214
215 @par Postconditions
216 The next `handshake()` uses `hostname` for SNI and
217 certificate verification, or neither if it is empty.
218
219 @note The hostname is used for client handshakes only;
220 it is ignored when handshaking as a server.
221
222 @param hostname The peer hostname, or empty to disable.
223 */
224 virtual void set_hostname(std::string_view hostname) = 0;
225
226 /** Return a reference to the underlying stream.
227
228 Provides access to the type-erased underlying stream for
229 operations like cancellation or accessing native handles.
230
231 @warning Do not reseat (assign to) the returned reference.
232 The TLS implementation holds internal state bound to
233 the original stream. Replacing it causes undefined
234 behavior.
235
236 @return Reference to the wrapped stream.
237 */
238 virtual capy::any_stream& next_layer() noexcept = 0;
239
240 /** Return a const reference to the underlying stream.
241
242 @return Const reference to the wrapped stream.
243 */
244 virtual capy::any_stream const& next_layer() const noexcept = 0;
245
246 /** Return the name of the TLS backend.
247
248 @return A string identifying the TLS implementation,
249 such as "openssl" or "wolfssl".
250 */
251 virtual std::string_view name() const noexcept = 0;
252
253 /** Return the ALPN protocol negotiated during the handshake.
254
255 Application-Layer Protocol Negotiation selects a single
256 application protocol (for example `"h2"` or `"http/1.1"`)
257 during the TLS handshake, from the list supplied via
258 @ref tls_context::set_alpn.
259
260 @return The negotiated protocol, or an empty view. It is empty
261 if no protocol was negotiated or ALPN was not offered. It is
262 also empty if the handshake has not completed, or if the build
263 lacks ALPN support.
264
265 @par Thread Safety
266 Safe to call after the handshake completes; not safe to call
267 concurrently with a handshake or reset.
268 */
269 virtual std::string_view alpn_protocol() const noexcept
270 {
271 return {};
272 } // LCOV_EXCL_LINE every concrete stream overrides this; the base default is never called
273
274 protected:
275 /// Default construct; a derived class supplies the session.
276 tls_stream() = default;
277
278 /** Perform the backend-specific decrypted read.
279
280 Derived classes override this to perform TLS decryption
281 and read operations.
282
283 @param buffers Buffer sequence to read into.
284
285 @return An awaitable yielding `(error_code,std::size_t)`.
286 */
287 virtual capy::io_task<std::size_t> do_read_some(
288 capy::detail::mutable_buffer_array<capy::detail::max_iovec_>
289 buffers) = 0;
290
291 /** Perform the backend-specific encrypted write.
292
293 Derived classes override this to perform TLS encryption
294 and write operations.
295
296 @param buffers Buffer sequence to write from.
297
298 @return An awaitable yielding `(error_code,std::size_t)`.
299 */
300 virtual capy::io_task<std::size_t> do_write_some(
301 capy::detail::const_buffer_array<capy::detail::max_iovec_> buffers) = 0;
302 };
303
304 } // namespace boost::corosio
305
306 #endif
307