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 /** 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 @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 will be destroyed when this
76 openssl_stream is destroyed.
77
78 @param stream The stream to take ownership of. Must satisfy
79 `capy::Stream`.
80 @param ctx The TLS context containing configuration.
81 */
82 template<capy::Stream S>
83 requires(!std::same_as<std::decay_t<S>, openssl_stream>)
84 openssl_stream(S stream, tls_context const& ctx)
85 : stream_(std::move(stream))
86 , impl_(make_implementation(stream_, ctx))
87 {
88 }
89
90 /** Construct an OpenSSL stream (reference mode).
91
92 Wraps the underlying stream without taking ownership. The
93 caller must ensure the stream remains valid for the lifetime
94 of this openssl_stream.
95
96 @param stream Pointer to the stream to wrap. Must satisfy
97 `capy::Stream`.
98 @param ctx The TLS context containing configuration.
99 */
100 template<capy::Stream S>
101 4182x openssl_stream(S* stream, tls_context const& ctx)
102
5/10
✓ Branch 0 taken 309 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1775 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.
2091x : stream_(stream)
103
5/10
✓ Branch 0 taken 309 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1775 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.
2091x , impl_(make_implementation(stream_, ctx))
104 4182x {
105 4182x }
106
107 /** Destructor.
108
109 Releases the underlying OpenSSL resources. If constructed
110 in owning mode, also destroys the underlying stream.
111 */
112 ~openssl_stream() override;
113
114 /** Move construct from another OpenSSL stream.
115
116 @param other The source stream. After the move,
117 @p other may only be destroyed or assigned to.
118 */
119 openssl_stream(openssl_stream&& other) noexcept;
120
121 /** Move assign from another OpenSSL stream.
122
123 @param other The source stream. After the move,
124 @p other may only be destroyed or assigned to.
125
126 @return `*this`.
127 */
128 openssl_stream& operator=(openssl_stream&& other) noexcept;
129
130 /** Asynchronously perform the TLS handshake.
131
132 Suspends the calling coroutine until the handshake
133 completes, an error occurs, or the operation is
134 cancelled via stop token.
135
136 @par Preconditions
137 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 @par Preconditions
153 A handshake must have completed successfully. May overlap
154 a pending read; the read completes with `capy::error::eof`
155 when the peer answers the close_notify. No concurrent write
156 may be in progress.
157
158 @par Postconditions
159 If the transport ends before the peer's close_notify is
160 received, the result is `capy::error::stream_truncated`, not
161 success. A shutdown stopped mid-flight reports canceled; any
162 other transport error propagates unchanged.
163
164 @return An awaitable yielding `(error_code)`.
165 */
166 [[nodiscard]] capy::io_task<> shutdown() override;
167
168 /** Reset TLS session state for reuse.
169
170 Clears internal buffers and session data so the stream
171 can perform a new handshake on the same underlying
172 connection. The previous session is discarded and never
173 resumed, so a handshake after `reset()` is always a full
174 handshake.
175
176 @par Preconditions
177 No TLS operation may be in progress on this stream.
178
179 @note If the backend cannot restore a clean session state,
180 subsequent handshakes fail rather than proceed on a
181 partially cleared session.
182 */
183 void reset() override;
184
185 /// Set the peer hostname for SNI and certificate verification.
186 void set_hostname(std::string_view hostname) override;
187
188 /// Return the underlying stream.
189 1x capy::any_stream& next_layer() noexcept override
190 {
191 1x return stream_;
192 }
193
194 /// Return the underlying stream.
195 1x capy::any_stream const& next_layer() const noexcept override
196 {
197 1x return stream_;
198 }
199
200 /// Return the TLS backend name ("openssl").
201 std::string_view name() const noexcept override;
202
203 /// Return the ALPN protocol negotiated during the handshake, or empty.
204 std::string_view alpn_protocol() const noexcept override;
205
206 protected:
207 capy::io_task<std::size_t> do_read_some(
208 capy::detail::mutable_buffer_array<capy::detail::max_iovec_> buffers)
209 override;
210
211 capy::io_task<std::size_t> do_write_some(
212 capy::detail::const_buffer_array<capy::detail::max_iovec_> buffers)
213 override;
214
215 private:
216 static implementation*
217 make_implementation(capy::any_stream& stream, tls_context const& ctx);
218 };
219
220 /** Return the error category for raw OpenSSL errors.
221
222 Errors reported by @ref openssl_stream that originate from the OpenSSL
223 error queue (`ERR_get_error`) are assigned this category. Its
224 `message()` decodes the packed OpenSSL error code using OpenSSL's own
225 diagnostic strings, so printing such an `error_code` yields a readable
226 description (for example, "certificate verify failed").
227
228 OpenSSL errors whose library is `ERR_LIB_SYS` are reported with
229 `std::system_category()` instead, since their reason code is a genuine
230 `errno` value.
231
232 @return A reference to a static category object with name
233 `"corosio.openssl"`.
234 */
235 BOOST_COROSIO_DECL std::error_category const& openssl_category() noexcept;
236
237 } // namespace boost::corosio
238
239 #endif
240