include/boost/corosio/openssl_stream.hpp

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