include/boost/corosio/openssl_stream.hpp

100.0% Lines (8/0/8) 100.0% List of functions (6/0/6)
openssl_stream.hpp
f(x) Functions (6)
Line 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 auto [ec] = co_await sock.connect(endpoint);
64 if (ec)
65 co_return;
66
67 // Reference mode - sock must outlive tls
68 corosio::openssl_stream tls(&sock, ctx);
69 tls.set_hostname("example.com");
70 auto [hec] = co_await tls.handshake(tls_role::client);
71 if (hec)
72 co_return;
73
74 // Or owning mode - tls owns the socket
75 corosio::openssl_stream tls2(std::move(sock), ctx);
76 @endcode
77
78 @see tls_stream, wolfssl_stream
79 */
80 class BOOST_COROSIO_DECL openssl_stream final : public tls_stream
81 {
82 struct implementation;
83 BOOST_COROSIO_MSVC_WARNING_PUSH
84 BOOST_COROSIO_MSVC_WARNING_DISABLE(4251) // capy::any_stream, dll-interface
85 capy::any_stream stream_; // must be first - impl_ holds reference
86 BOOST_COROSIO_MSVC_WARNING_POP
87 implementation* impl_;
88
89 public:
90 /** Construct an OpenSSL stream (owning mode).
91
92 Takes ownership of the underlying stream by moving it into
93 internal storage. The stream will be destroyed when this
94 openssl_stream is destroyed.
95
96 @param stream The stream to take ownership of. Must satisfy
97 `capy::Stream`.
98 @param ctx The TLS context containing configuration.
99 */
100 template<capy::Stream S>
101 requires(!std::same_as<std::decay_t<S>, openssl_stream>)
102 openssl_stream(S stream, tls_context const& ctx)
103 : stream_(std::move(stream))
104 , impl_(make_implementation(stream_, ctx))
105 {
106 }
107
108 /** Construct an OpenSSL stream (reference mode).
109
110 Wraps the underlying stream without taking ownership. The
111 caller must ensure the stream remains valid for the lifetime
112 of this openssl_stream.
113
114 @param stream Pointer to the stream to wrap. Must satisfy
115 `capy::Stream`.
116 @param ctx The TLS context containing configuration.
117 */
118 template<capy::Stream S>
119 3281x openssl_stream(S* stream, tls_context const& ctx)
120 3281x : stream_(stream)
121 3281x , impl_(make_implementation(stream_, ctx))
122 {
123 3281x }
124
125 /** Destructor.
126
127 Releases the underlying OpenSSL resources. If constructed
128 in owning mode, also destroys the underlying stream.
129 */
130 ~openssl_stream() override;
131
132 /** Move construct from another OpenSSL stream.
133
134 @param other The source stream. After the move,
135 @p other may only be destroyed or assigned to.
136 */
137 openssl_stream(openssl_stream&& other) noexcept;
138
139 /** Move assign from another OpenSSL stream.
140
141 @param other The source stream. After the move,
142 @p other may only be destroyed or assigned to.
143
144 @return `*this`.
145 */
146 openssl_stream& operator=(openssl_stream&& other) noexcept;
147
148 /** Asynchronously perform the TLS handshake.
149
150 Suspends the calling coroutine until the handshake
151 completes, an error occurs, or the operation is
152 cancelled via stop token.
153
154 @par Preconditions
155 The underlying stream must be connected. No other
156 TLS operation may be in progress on this stream.
157
158 @param role The handshake role, client or server.
159
160 @return An awaitable yielding `(error_code)`.
161 */
162 [[nodiscard]] capy::io_task<> handshake(tls_role role) override;
163
164 /** Asynchronously shut down the TLS session.
165
166 Sends a close_notify alert and waits for the peer's
167 close_notify response. Supports cancellation via
168 stop token.
169
170 @par Preconditions
171 A handshake must have completed successfully. May overlap
172 a pending read; the read completes with `capy::error::eof`
173 when the peer answers the close_notify. No concurrent write
174 may be in progress.
175
176 @par Postconditions
177 If the transport ends before the peer's close_notify is
178 received, the result is `capy::error::stream_truncated`, not
179 success. A shutdown stopped mid-flight reports canceled; any
180 other transport error propagates unchanged.
181
182 @return An awaitable yielding `(error_code)`.
183 */
184 [[nodiscard]] capy::io_task<> shutdown() override;
185
186 /** Reset TLS session state for reuse.
187
188 Clears internal buffers and session data so the stream
189 can perform a new handshake on the same underlying
190 connection. The previous session is discarded and never
191 resumed, so a handshake after `reset()` is always a full
192 handshake.
193
194 @par Preconditions
195 No TLS operation may be in progress on this stream.
196
197 @note If the backend cannot restore a clean session state,
198 subsequent handshakes fail rather than proceed on a
199 partially cleared session.
200 */
201 void reset() override;
202
203 /// Set the peer hostname for SNI and certificate verification.
204 void set_hostname(std::string_view hostname) override;
205
206 /// Return the underlying stream.
207 1x capy::any_stream& next_layer() noexcept override
208 {
209 1x return stream_;
210 }
211
212 /// Return the underlying stream.
213 1x capy::any_stream const& next_layer() const noexcept override
214 {
215 1x return stream_;
216 }
217
218 /// Return the TLS backend name ("openssl").
219 std::string_view name() const noexcept override;
220
221 /// Return the ALPN protocol negotiated during the handshake, or empty.
222 std::string_view alpn_protocol() const noexcept override;
223
224 protected:
225 capy::io_task<std::size_t> do_read_some(
226 capy::detail::mutable_buffer_array<capy::detail::max_iovec_> buffers) override;
227
228 capy::io_task<std::size_t> do_write_some(
229 capy::detail::const_buffer_array<capy::detail::max_iovec_> buffers) override;
230
231 private:
232 static implementation* make_implementation(capy::any_stream& stream, tls_context const& ctx);
233 };
234
235 /** Return the error category for raw OpenSSL errors.
236
237 Errors reported by @ref openssl_stream that originate from the OpenSSL
238 error queue (`ERR_get_error`) are assigned this category. Its
239 `message()` decodes the packed OpenSSL error code using OpenSSL's own
240 diagnostic strings, so printing such an `error_code` yields a readable
241 description (for example, "certificate verify failed").
242
243 OpenSSL errors whose library is `ERR_LIB_SYS` are reported with
244 `std::system_category()` instead, since their reason code is a genuine
245 `errno` value.
246
247 @return A reference to a static category object with name
248 `"corosio.openssl"`.
249 */
250 BOOST_COROSIO_DECL std::error_category const&
251 openssl_category() noexcept;
252
253 } // namespace boost::corosio
254
255 #endif
256