include/boost/corosio/openssl_stream.hpp

100.0% Lines (9/9) 100.0% List of functions (6/6) 50.0% Branches (8/16)
openssl_stream.hpp
f(x) Functions (6)
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco ([email protected])
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_OPENSSL_STREAM_HPP
13 #define BOOST_COROSIO_OPENSSL_STREAM_HPP
14
15 #include <boost/corosio/detail/config.hpp>
16 #include <boost/corosio/tls_context.hpp>
17 #include <boost/corosio/tls_stream.hpp>
18 #include <boost/capy/detail/buffer_array.hpp>
19 #include <boost/capy/concept/stream.hpp>
20 #include <boost/capy/io/any_stream.hpp>
21 #include <boost/capy/io_task.hpp>
22
23 #include <concepts>
24 #include <system_error>
25
26 namespace boost::corosio {
27
28 /** A TLS stream using OpenSSL.
29
30 This class wraps an underlying stream satisfying `capy::Stream`
31 and provides TLS encryption using the OpenSSL library.
32
33 Derives from @ref tls_stream to provide a runtime-polymorphic
34 interface. The TLS operations are implemented as coroutines
35 that orchestrate reads and writes on the underlying stream.
36
37 @par Construction Modes
38
39 Two construction modes are supported:
40
41 - **Owning**: Pass stream by value. The openssl_stream takes
42 ownership and the stream is moved into internal storage.
43
44 - **Reference**: Pass stream by pointer. The openssl_stream
45 does not own the stream; the caller must ensure the stream
46 outlives this object.
47
48 @par Thread Safety
49 Distinct objects: Safe.@n
50 Shared objects: Unsafe, with one exception: one read operation and
51 one write operation may be in flight simultaneously. `shutdown()`
52 may overlap a pending read. When the execution context runs on
53 multiple threads, all operations on one stream must be performed
54 within the same `capy::strand` (or otherwise never run
55 concurrently); a single-threaded context needs no strand.
56
57 @par Example
58 @code
59 tls_context ctx;
60 ctx.set_verify_mode(tls_verify_mode::peer);
61
62 corosio::tcp_socket sock(ioc);
63 co_await sock.connect(endpoint);
64
65 // Reference mode - sock must outlive tls
66 corosio::openssl_stream tls(&sock, ctx);
67 tls.set_hostname("example.com");
68 auto [ec] = co_await tls.handshake(tls_role::client);
69
70 // Or owning mode - tls owns the socket
71 corosio::openssl_stream tls2(std::move(sock), ctx);
72 @endcode
73
74 @see tls_stream, wolfssl_stream
75 */
76 class BOOST_COROSIO_DECL openssl_stream final : public tls_stream
77 {
78 struct implementation;
79 BOOST_COROSIO_MSVC_WARNING_PUSH
80 BOOST_COROSIO_MSVC_WARNING_DISABLE(4251) // capy::any_stream, dll-interface
81 capy::any_stream stream_; // must be first - impl_ holds reference
82 BOOST_COROSIO_MSVC_WARNING_POP
83 implementation* impl_;
84
85 public:
86 /** Construct an OpenSSL stream (owning mode).
87
88 Takes ownership of the underlying stream by moving it into
89 internal storage. The stream will be destroyed when this
90 openssl_stream is destroyed.
91
92 @param stream The stream to take ownership of. Must satisfy
93 `capy::Stream`.
94 @param ctx The TLS context containing configuration.
95 */
96 template<capy::Stream S>
97 requires(!std::same_as<std::decay_t<S>, openssl_stream>)
98 openssl_stream(S stream, tls_context const& ctx)
99 : stream_(std::move(stream))
100 , impl_(make_implementation(stream_, ctx))
101 {
102 }
103
104 /** Construct an OpenSSL stream (reference mode).
105
106 Wraps the underlying stream without taking ownership. The
107 caller must ensure the stream remains valid for the lifetime
108 of this openssl_stream.
109
110 @param stream Pointer to the stream to wrap. Must satisfy
111 `capy::Stream`.
112 @param ctx The TLS context containing configuration.
113 */
114 template<capy::Stream S>
115 5564x openssl_stream(S* stream, tls_context const& ctx)
116
4/8
✓ Branch 0 taken 283 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2497 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
2782x : stream_(stream)
117
4/8
✓ Branch 0 taken 283 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2497 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
2782x , impl_(make_implementation(stream_, ctx))
118 5564x {
119 5564x }
120
121 /** Destructor.
122
123 Releases the underlying OpenSSL resources. If constructed
124 in owning mode, also destroys the underlying stream.
125 */
126 ~openssl_stream() override;
127
128 /** Move construct from another OpenSSL stream.
129
130 @param other The source stream. After the move,
131 @p other is in a valid but unspecified state.
132 */
133 openssl_stream(openssl_stream&& other) noexcept;
134
135 /** Move assign from another OpenSSL stream.
136
137 @param other The source stream. After the move,
138 @p other is in a valid but unspecified state.
139
140 @return `*this`.
141 */
142 openssl_stream& operator=(openssl_stream&& other) noexcept;
143
144 /** Asynchronously perform the TLS handshake.
145
146 Suspends the calling coroutine until the handshake
147 completes, an error occurs, or the operation is
148 cancelled via stop token.
149
150 @par Preconditions
151 The underlying stream must be connected. No other
152 TLS operation may be in progress on this stream.
153
154 @param role The handshake role, client or server.
155
156 @return An awaitable yielding `(error_code)`.
157 */
158 capy::io_task<> handshake(tls_role role) override;
159
160 /** Asynchronously shut down the TLS session.
161
162 Sends a close_notify alert and waits for the peer's
163 close_notify response. Supports cancellation via
164 stop token.
165
166 @par Preconditions
167 A handshake must have completed successfully. May overlap
168 a pending read; the read completes with `capy::error::eof`
169 when the peer answers the close_notify. No concurrent write
170 may be in progress.
171
172 @par Postconditions
173 If the transport ends before the peer's close_notify is
174 received, the result is `capy::error::stream_truncated`, not
175 success. A shutdown stopped mid-flight reports canceled; any
176 other transport error propagates unchanged.
177
178 @return An awaitable yielding `(error_code)`.
179 */
180 capy::io_task<> shutdown() override;
181
182 /** Reset TLS session state for reuse.
183
184 Clears internal buffers and session data so the stream
185 can perform a new handshake on the same underlying
186 connection. The previous session is discarded and never
187 resumed, so a handshake after `reset()` is always a full
188 handshake.
189
190 @par Preconditions
191 No TLS operation may be in progress on this stream.
192
193 @note If the backend cannot restore a clean session state,
194 subsequent handshakes fail rather than proceed on a
195 partially cleared session.
196 */
197 void reset() override;
198
199 /// Set the peer hostname for SNI and certificate verification.
200 void set_hostname(std::string_view hostname) override;
201
202 /// Return the underlying stream.
203 1x capy::any_stream& next_layer() noexcept override
204 {
205 1x return stream_;
206 }
207
208 /// Return the underlying stream.
209 1x capy::any_stream const& next_layer() const noexcept override
210 {
211 1x return stream_;
212 }
213
214 /// Return the TLS backend name ("openssl").
215 std::string_view name() const noexcept override;
216
217 /// Return the ALPN protocol negotiated during the handshake, or empty.
218 std::string_view alpn_protocol() const noexcept override;
219
220 protected:
221 capy::io_task<std::size_t> do_read_some(
222 capy::detail::mutable_buffer_array<capy::detail::max_iovec_> buffers) override;
223
224 capy::io_task<std::size_t> do_write_some(
225 capy::detail::const_buffer_array<capy::detail::max_iovec_> buffers) override;
226
227 private:
228 static implementation* make_implementation(capy::any_stream& stream, tls_context const& ctx);
229 };
230
231 /** Return the error category for raw OpenSSL errors.
232
233 Errors reported by @ref openssl_stream that originate from the OpenSSL
234 error queue (`ERR_get_error`) are assigned this category. Its
235 `message()` decodes the packed OpenSSL error code using OpenSSL's own
236 diagnostic strings, so printing such an `error_code` yields a readable
237 description (for example, "certificate verify failed").
238
239 OpenSSL errors whose library is `ERR_LIB_SYS` are reported with
240 `std::system_category()` instead, since their reason code is a genuine
241 `errno` value.
242
243 @return A reference to a static category object with name
244 `"corosio.openssl"`.
245 */
246 BOOST_COROSIO_DECL std::error_category const&
247 openssl_category() noexcept;
248
249 } // namespace boost::corosio
250
251 #endif
252