include/boost/corosio/openssl_stream.hpp

100.0% Lines (9/9) 100.0% List of functions (4/4) 50.0% Branches (4/8)
openssl_stream.hpp
f(x) Functions (4)
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco ([email protected])
3 // Copyright (c) 2026 Michael Vandeberg
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/corosio
9 //
10
11 #ifndef BOOST_COROSIO_OPENSSL_STREAM_HPP
12 #define BOOST_COROSIO_OPENSSL_STREAM_HPP
13
14 #include <boost/corosio/detail/config.hpp>
15 #include <boost/corosio/tls_context.hpp>
16 #include <boost/corosio/tls_stream.hpp>
17 #include <boost/capy/detail/buffer_array.hpp>
18 #include <boost/capy/concept/stream.hpp>
19 #include <boost/capy/io/any_stream.hpp>
20 #include <boost/capy/io_task.hpp>
21
22 #include <concepts>
23 #include <system_error>
24
25 namespace boost::corosio {
26
27 /** A TLS stream using OpenSSL.
28
29 This class wraps an underlying stream satisfying `capy::Stream`
30 and provides TLS encryption using the OpenSSL library.
31
32 Derives from @ref tls_stream to provide a runtime-polymorphic
33 interface. The TLS operations are implemented as coroutines
34 that orchestrate reads and writes on the underlying stream.
35
36 @par Construction Modes
37
38 Two construction modes are supported:
39
40 - **Owning**: Pass stream by value. The openssl_stream takes
41 ownership and the stream is moved into internal storage.
42
43 - **Reference**: Pass stream by pointer. The openssl_stream
44 does not own the stream; the caller must ensure the stream
45 outlives this object.
46
47 @par Thread Safety
48 Distinct objects: Safe.@n
49 Shared objects: Unsafe.
50
51 @par Example
52 @code
53 tls_context ctx;
54 ctx.set_hostname("example.com");
55 ctx.set_verify_mode(tls_verify_mode::peer);
56
57 corosio::tcp_socket sock(ioc);
58 co_await sock.connect(endpoint);
59
60 // Reference mode - sock must outlive tls
61 corosio::openssl_stream tls(&sock, ctx);
62 auto [ec] = co_await tls.handshake(openssl_stream::client);
63
64 // Or owning mode - tls owns the socket
65 corosio::openssl_stream tls2(std::move(sock), ctx);
66 @endcode
67
68 @see tls_stream, wolfssl_stream
69 */
70 class BOOST_COROSIO_DECL openssl_stream final : public tls_stream
71 {
72 struct impl;
73 BOOST_COROSIO_MSVC_WARNING_PUSH
74 BOOST_COROSIO_MSVC_WARNING_DISABLE(4251) // capy::any_stream, dll-interface
75 capy::any_stream stream_; // must be first - impl_ holds reference
76 BOOST_COROSIO_MSVC_WARNING_POP
77 impl* impl_;
78
79 public:
80 /** Construct an OpenSSL stream (owning mode).
81
82 Takes ownership of the underlying stream by moving it into
83 internal storage. The stream will be destroyed when this
84 openssl_stream is destroyed.
85
86 @param stream The stream to take ownership of. Must satisfy
87 `capy::Stream`.
88 @param ctx The TLS context containing configuration.
89 */
90 template<capy::Stream S>
91 requires(!std::same_as<std::decay_t<S>, openssl_stream>)
92 openssl_stream(S stream, tls_context const& ctx)
93 : stream_(std::move(stream))
94 , impl_(make_impl(stream_, ctx))
95 {
96 }
97
98 /** Construct an OpenSSL stream (reference mode).
99
100 Wraps the underlying stream without taking ownership. The
101 caller must ensure the stream remains valid for the lifetime
102 of this openssl_stream.
103
104 @param stream Pointer to the stream to wrap. Must satisfy
105 `capy::Stream`.
106 @param ctx The TLS context containing configuration.
107 */
108 template<capy::Stream S>
109 3792x openssl_stream(S* stream, tls_context const& ctx)
110
2/4
✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1849 times.
✗ Branch 3 not taken.
1896x : stream_(stream)
111
2/4
✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1849 times.
✗ Branch 3 not taken.
1896x , impl_(make_impl(stream_, ctx))
112 3792x {
113 3792x }
114
115 /** Destructor.
116
117 Releases the underlying OpenSSL resources. If constructed
118 in owning mode, also destroys the underlying stream.
119 */
120 ~openssl_stream() override;
121
122 /** Move construct from another OpenSSL stream.
123
124 @param other The source stream. After the move,
125 @p other is in a valid but unspecified state.
126 */
127 openssl_stream(openssl_stream&& other) noexcept;
128
129 /** Move assign from another OpenSSL stream.
130
131 @param other The source stream. After the move,
132 @p other is in a valid but unspecified state.
133
134 @return `*this`.
135 */
136 openssl_stream& operator=(openssl_stream&& other) noexcept;
137
138 /** Perform the TLS handshake asynchronously.
139
140 Suspends the calling coroutine until the handshake
141 completes, an error occurs, or the operation is
142 cancelled via stop token.
143
144 @par Preconditions
145 The underlying stream must be connected. No other
146 TLS operation may be in progress on this stream.
147
148 @param type The handshake role (client or server).
149
150 @return An awaitable yielding `(error_code)`.
151 */
152 capy::io_task<> handshake(handshake_type type) override;
153
154 /** Shut down the TLS session asynchronously.
155
156 Sends a close_notify alert and waits for the peer's
157 close_notify response. Supports cancellation via
158 stop token.
159
160 @par Preconditions
161 A handshake must have completed successfully. No
162 other TLS operation may be in progress on this stream.
163
164 @return An awaitable yielding `(error_code)`.
165 */
166 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.
173
174 @par Preconditions
175 No TLS operation may be in progress on this stream.
176 */
177 void reset() override;
178
179 /// Return the underlying stream.
180 1x capy::any_stream& next_layer() noexcept override
181 {
182 1x return stream_;
183 }
184
185 /// Return the underlying stream.
186 1x capy::any_stream const& next_layer() const noexcept override
187 {
188 1x return stream_;
189 }
190
191 /// Return the TLS backend name ("openssl").
192 std::string_view name() const noexcept override;
193
194 /// Return the ALPN protocol negotiated during the handshake, or empty.
195 std::string_view alpn_protocol() const noexcept override;
196
197 protected:
198 capy::io_task<std::size_t> do_read_some(
199 capy::detail::mutable_buffer_array<capy::detail::max_iovec_> buffers) override;
200
201 capy::io_task<std::size_t> do_write_some(
202 capy::detail::const_buffer_array<capy::detail::max_iovec_> buffers) override;
203
204 private:
205 static impl* make_impl(capy::any_stream& stream, tls_context const& ctx);
206 };
207
208 /** Return the error category for raw OpenSSL errors.
209
210 Errors reported by @ref openssl_stream that originate from the OpenSSL
211 error queue (`ERR_get_error`) are assigned this category. Its
212 `message()` decodes the packed OpenSSL error code using OpenSSL's own
213 diagnostic strings, so printing such an `error_code` yields a readable
214 description (for example, "certificate verify failed").
215
216 OpenSSL errors whose library is `ERR_LIB_SYS` are reported with
217 `std::system_category()` instead, since their reason code is a genuine
218 `errno` value.
219
220 @return A reference to a static category object with name
221 `"corosio.openssl"`.
222 */
223 BOOST_COROSIO_DECL std::error_category const&
224 openssl_category() noexcept;
225
226 } // namespace boost::corosio
227
228 #endif
229