src/openssl/src/openssl_stream.cpp

77.9% Lines (162/208) 89.7% Functions (26/29) 65.7% Branches (117/178)
src/openssl/src/openssl_stream.cpp
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco ([email protected])
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/corosio
8 //
9
10 #include <boost/corosio/openssl_stream.hpp>
11 #include <boost/corosio/detail/config.hpp>
12 #include <boost/capy/buffers/buffer_array.hpp>
13 #include <boost/capy/ex/async_mutex.hpp>
14 #include <boost/capy/error.hpp>
15 #include <boost/capy/write.hpp>
16
17 // Internal context implementation
18 #include "src/tls/detail/context_impl.hpp"
19
20 #include <openssl/ssl.h>
21 #include <openssl/err.h>
22 #include <openssl/bio.h>
23 #include <openssl/x509.h>
24
25 #include <algorithm>
26 #include <array>
27 #include <cstring>
28 #include <vector>
29
30 /*
31 openssl_stream Architecture
32 ===========================
33
34 TLS layer wrapping an underlying stream (via any_stream). Supports one
35 concurrent read_some and one concurrent write_some (like Asio's ssl::stream).
36
37 Data Flow (using BIO pairs)
38 ---------------------------
39 App -> SSL_write -> int_bio_ -> BIO_read(ext_bio_) -> out_buf_ -> s_.write_some -> Network
40 App <- SSL_read <- int_bio_ <- BIO_write(ext_bio_) <- in_buf_ <- s_.read_some <- Network
41
42 WANT_READ / WANT_WRITE Pattern
43 ------------------------------
44 OpenSSL's SSL_read/SSL_write return SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE
45 when they need I/O. Our coroutine handles this by:
46
47 1. Call SSL_read or SSL_write
48 2. Check for pending output in ext_bio_ via BIO_ctrl_pending
49 3. If output pending: write to network via s_.write_some
50 4. If SSL_ERROR_WANT_READ: read from network into ext_bio_ via s_.read_some + BIO_write
51 5. Loop back to step 1
52
53 Renegotiation causes cross-direction I/O: SSL_read may need to write
54 handshake data, SSL_write may need to read. Each operation handles
55 whatever I/O direction OpenSSL requests.
56 */
57
58 namespace boost::corosio {
59
60 namespace {
61
62 constexpr std::size_t default_buffer_size = 16384;
63
64 inline SSL_METHOD const*
65 2166 tls_method_compat() noexcept
66 {
67 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
68 2166 return TLS_method();
69 #else
70 return SSLv23_method();
71 #endif
72 }
73
74 inline void
75 2191 apply_hostname_verification(SSL* ssl, std::string const& hostname)
76 {
77
2/2
✓ Branch 3 → 4 taken 2187 times.
✓ Branch 3 → 5 taken 4 times.
2191 if (hostname.empty())
78 2187 return;
79
80 4 SSL_set_tlsext_host_name(ssl, hostname.c_str());
81
82 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
83 4 SSL_set1_host(ssl, hostname.c_str());
84 #else
85 if (auto* param = SSL_get0_param(ssl))
86 X509_VERIFY_PARAM_set1_host(param, hostname.c_str(), 0);
87 #endif
88 }
89
90 inline std::error_code
91 8 normalize_openssl_shutdown_read_error(std::error_code ec) noexcept
92 {
93
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 8 times.
8 if (!ec)
94 return ec;
95
96
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 20 taken 2 times.
10 if (ec == make_error_code(capy::error::eof) ||
97
0/2
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 20 not taken.
2 ec == make_error_code(capy::error::canceled) ||
98 ec == std::errc::connection_reset ||
99
3/6
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 20 taken 6 times.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 21 not taken.
✓ Branch 22 → 23 taken 8 times.
✗ Branch 22 → 24 not taken.
10 ec == std::errc::connection_aborted || ec == std::errc::broken_pipe)
100 8 return make_error_code(capy::error::stream_truncated);
101
102 return ec;
103 }
104
105 } // namespace
106
107 //
108 // Native context caching
109 //
110
111 namespace detail {
112
113 static int sni_ctx_data_index = -1;
114
115 static int
116 password_callback(char* buf, int size, int rwflag, void* userdata)
117 {
118 auto* cd = static_cast<tls_context_data const*>(userdata);
119 if (!cd || !cd->password_callback)
120 return 0;
121
122 tls_password_purpose purpose = (rwflag == 0)
123 ? tls_password_purpose::for_reading
124 : tls_password_purpose::for_writing;
125
126 std::string password =
127 cd->password_callback(static_cast<std::size_t>(size), purpose);
128
129 int len = static_cast<int>(password.size());
130 if (len > size)
131 len = size;
132
133 std::memcpy(buf, password.data(), static_cast<std::size_t>(len));
134 return len;
135 }
136
137 static int
138 2 sni_callback(SSL* ssl, int* /* alert */, void* /* arg */)
139 {
140 2 char const* servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
141
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (!servername)
142 return SSL_TLSEXT_ERR_NOACK;
143
144 2 SSL_CTX* ctx = SSL_get_SSL_CTX(ssl);
145 auto* cd = static_cast<tls_context_data const*>(
146 2 SSL_CTX_get_ex_data(ctx, sni_ctx_data_index));
147
148
3/6
✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 11 not taken.
✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 11 not taken.
✓ Branch 12 → 13 taken 2 times.
✗ Branch 12 → 17 not taken.
2 if (cd && cd->servername_callback)
149 {
150
3/3
✓ Branch 14 → 15 taken 2 times.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 1 time.
2 if (!cd->servername_callback(servername))
151 1 return SSL_TLSEXT_ERR_ALERT_FATAL;
152 }
153
154 1 return SSL_TLSEXT_ERR_OK;
155 }
156
157 class openssl_native_context : public native_context_base
158 {
159 public:
160 SSL_CTX* ctx_;
161 tls_context_data const* cd_;
162
163 2166 explicit openssl_native_context(tls_context_data const& cd)
164 4332 : ctx_(nullptr)
165 2166 , cd_(&cd)
166 {
167
1/1
✓ Branch 4 → 5 taken 2166 times.
2166 ctx_ = SSL_CTX_new(tls_method_compat());
168
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 2166 times.
2166 if (!ctx_)
169 return;
170
171
2/2
✓ Branch 7 → 8 taken 4 times.
✓ Branch 7 → 10 taken 2162 times.
2166 if (sni_ctx_data_index < 0)
172 4 sni_ctx_data_index =
173
1/1
✓ Branch 8 → 9 taken 4 times.
4 SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
174
175
1/1
✓ Branch 10 → 11 taken 2166 times.
2166 SSL_CTX_set_ex_data(
176 ctx_, sni_ctx_data_index, const_cast<tls_context_data*>(&cd));
177
178
2/2
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 14 taken 2164 times.
2166 if (cd.servername_callback)
179
1/1
✓ Branch 13 → 14 taken 2 times.
2 SSL_CTX_set_tlsext_servername_callback(ctx_, sni_callback);
180
181
1/1
✓ Branch 14 → 15 taken 2166 times.
2166 SSL_CTX_set_mode(ctx_, SSL_MODE_ENABLE_PARTIAL_WRITE);
182
1/1
✓ Branch 15 → 16 taken 2166 times.
2166 SSL_CTX_set_mode(ctx_, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
183 #if defined(SSL_MODE_RELEASE_BUFFERS)
184
1/1
✓ Branch 16 → 17 taken 2166 times.
2166 SSL_CTX_set_mode(ctx_, SSL_MODE_RELEASE_BUFFERS);
185 #endif
186
187 2166 int verify_mode_flag = SSL_VERIFY_NONE;
188
2/2
✓ Branch 17 → 18 taken 1079 times.
✓ Branch 17 → 19 taken 1087 times.
2166 if (cd.verification_mode == tls_verify_mode::peer)
189 1079 verify_mode_flag = SSL_VERIFY_PEER;
190
2/2
✓ Branch 19 → 20 taken 3 times.
✓ Branch 19 → 21 taken 1084 times.
1087 else if (cd.verification_mode == tls_verify_mode::require_peer)
191 3 verify_mode_flag =
192 SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
193
1/1
✓ Branch 21 → 22 taken 2166 times.
2166 SSL_CTX_set_verify(ctx_, verify_mode_flag, nullptr);
194
195
2/2
✓ Branch 23 → 24 taken 1084 times.
✓ Branch 23 → 37 taken 1082 times.
2166 if (!cd.entity_certificate.empty())
196 {
197
1/1
✓ Branch 26 → 27 taken 1084 times.
1084 BIO* bio = BIO_new_mem_buf(
198 1084 cd.entity_certificate.data(),
199 1084 static_cast<int>(cd.entity_certificate.size()));
200
1/2
✓ Branch 27 → 28 taken 1084 times.
✗ Branch 27 → 37 not taken.
1084 if (bio)
201 {
202 1084 X509* cert = nullptr;
203
1/2
✓ Branch 28 → 29 taken 1084 times.
✗ Branch 28 → 31 not taken.
1084 if (cd.entity_cert_format == tls_file_format::pem)
204
1/1
✓ Branch 29 → 30 taken 1084 times.
1084 cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
205 else
206 cert = d2i_X509_bio(bio, nullptr);
207
1/2
✓ Branch 33 → 34 taken 1084 times.
✗ Branch 33 → 36 not taken.
1084 if (cert)
208 {
209
1/1
✓ Branch 34 → 35 taken 1084 times.
1084 SSL_CTX_use_certificate(ctx_, cert);
210
1/1
✓ Branch 35 → 36 taken 1084 times.
1084 X509_free(cert);
211 }
212
1/1
✓ Branch 36 → 37 taken 1084 times.
1084 BIO_free(bio);
213 }
214 }
215
216
2/2
✓ Branch 38 → 39 taken 1 time.
✓ Branch 38 → 53 taken 2165 times.
2166 if (!cd.certificate_chain.empty())
217 {
218
1/1
✓ Branch 41 → 42 taken 1 time.
1 BIO* bio = BIO_new_mem_buf(
219 1 cd.certificate_chain.data(),
220 1 static_cast<int>(cd.certificate_chain.size()));
221
1/2
✓ Branch 42 → 43 taken 1 time.
✗ Branch 42 → 53 not taken.
1 if (bio)
222 {
223 X509* entity =
224
1/1
✓ Branch 43 → 44 taken 1 time.
1 PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
225
1/2
✓ Branch 44 → 45 taken 1 time.
✗ Branch 44 → 47 not taken.
1 if (entity)
226 {
227
1/1
✓ Branch 45 → 46 taken 1 time.
1 SSL_CTX_use_certificate(ctx_, entity);
228
1/1
✓ Branch 46 → 47 taken 1 time.
1 X509_free(entity);
229 }
230
231 X509* cert;
232
1/1
✓ Branch 49 → 50 taken 2 times.
2 while ((cert = PEM_read_bio_X509(
233
2/2
✓ Branch 50 → 48 taken 1 time.
✓ Branch 50 → 51 taken 1 time.
2 bio, nullptr, nullptr, nullptr)) != nullptr)
234 {
235
1/1
✓ Branch 48 → 49 taken 1 time.
1 SSL_CTX_add_extra_chain_cert(ctx_, cert);
236 }
237
1/1
✓ Branch 51 → 52 taken 1 time.
1 ERR_clear_error();
238
1/1
✓ Branch 52 → 53 taken 1 time.
1 BIO_free(bio);
239 }
240 }
241
242
2/2
✓ Branch 54 → 55 taken 1085 times.
✓ Branch 54 → 72 taken 1081 times.
2166 if (!cd.private_key.empty())
243 {
244
1/1
✓ Branch 57 → 58 taken 1085 times.
1085 BIO* bio = BIO_new_mem_buf(
245 1085 cd.private_key.data(), static_cast<int>(cd.private_key.size()));
246
1/2
✓ Branch 58 → 59 taken 1085 times.
✗ Branch 58 → 72 not taken.
1085 if (bio)
247 {
248 1085 EVP_PKEY* pkey = nullptr;
249
1/2
✓ Branch 59 → 60 taken 1085 times.
✗ Branch 59 → 66 not taken.
1085 if (cd.private_key_format == tls_file_format::pem)
250 {
251
1/2
✗ Branch 61 → 62 not taken.
✓ Branch 61 → 64 taken 1085 times.
1085 if (cd.password_callback)
252 pkey = PEM_read_bio_PrivateKey(
253 bio, nullptr, password_callback,
254 const_cast<tls_context_data*>(&cd));
255 else
256
1/1
✓ Branch 64 → 65 taken 1085 times.
1085 pkey = PEM_read_bio_PrivateKey(
257 bio, nullptr, nullptr, nullptr);
258 }
259 else
260 pkey = d2i_PrivateKey_bio(bio, nullptr);
261
1/2
✓ Branch 68 → 69 taken 1085 times.
✗ Branch 68 → 71 not taken.
1085 if (pkey)
262 {
263
1/1
✓ Branch 69 → 70 taken 1085 times.
1085 SSL_CTX_use_PrivateKey(ctx_, pkey);
264
1/1
✓ Branch 70 → 71 taken 1085 times.
1085 EVP_PKEY_free(pkey);
265 }
266
1/1
✓ Branch 71 → 72 taken 1085 times.
1085 BIO_free(bio);
267 }
268 }
269
270
1/1
✓ Branch 72 → 73 taken 2166 times.
2166 X509_STORE* store = SSL_CTX_get_cert_store(ctx_);
271
2/2
✓ Branch 87 → 75 taken 1093 times.
✓ Branch 87 → 88 taken 2166 times.
3259 for (auto const& ca : cd.ca_certificates)
272 {
273
1/1
✓ Branch 78 → 79 taken 1093 times.
1093 BIO* bio = BIO_new_mem_buf(ca.data(), static_cast<int>(ca.size()));
274
1/2
✓ Branch 79 → 80 taken 1093 times.
✗ Branch 79 → 85 not taken.
1093 if (bio)
275 {
276
1/1
✓ Branch 80 → 81 taken 1093 times.
1093 X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
277
2/2
✓ Branch 81 → 82 taken 1092 times.
✓ Branch 81 → 84 taken 1 time.
1093 if (cert)
278 {
279
1/1
✓ Branch 82 → 83 taken 1092 times.
1092 X509_STORE_add_cert(store, cert);
280
1/1
✓ Branch 83 → 84 taken 1092 times.
1092 X509_free(cert);
281 }
282
1/1
✓ Branch 84 → 85 taken 1093 times.
1093 BIO_free(bio);
283 }
284 }
285
286
1/1
✓ Branch 88 → 89 taken 2166 times.
2166 SSL_CTX_set_verify_depth(ctx_, cd.verify_depth);
287
288
2/2
✓ Branch 90 → 91 taken 3 times.
✓ Branch 90 → 94 taken 2163 times.
2166 if (!cd.ciphersuites.empty())
289 {
290
1/1
✓ Branch 91 → 92 taken 3 times.
3 SSL_CTX_set_security_level(ctx_, 0);
291
1/1
✓ Branch 93 → 94 taken 3 times.
3 SSL_CTX_set_cipher_list(ctx_, cd.ciphersuites.c_str());
292 }
293 }
294
295 4332 ~openssl_native_context() override
296 2166 {
297
1/2
✓ Branch 2 → 3 taken 2166 times.
✗ Branch 2 → 4 not taken.
2166 if (ctx_)
298 2166 SSL_CTX_free(ctx_);
299 4332 }
300 };
301
302 inline SSL_CTX*
303 2171 get_openssl_context(tls_context_data const& cd)
304 {
305 static char key;
306
2/4
✓ Branch 2 → 3 taken 2171 times.
✓ Branch 3 → 4 taken 2166 times.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
4337 auto* p = cd.find(&key, [&] { return new openssl_native_context(cd); });
307 2171 return static_cast<openssl_native_context*>(p)->ctx_;
308 }
309
310 } // namespace detail
311
312 struct openssl_stream::impl
313 {
314 capy::any_stream& s_;
315 tls_context ctx_;
316 SSL* ssl_ = nullptr;
317 BIO* ext_bio_ = nullptr;
318 bool used_ = false;
319
320 std::vector<char> in_buf_;
321 std::vector<char> out_buf_;
322
323 capy::async_mutex io_cm_;
324
325 2171 impl(capy::any_stream& s, tls_context ctx) : s_(s), ctx_(std::move(ctx))
326 {
327
1/1
✓ Branch 7 → 8 taken 2171 times.
2171 in_buf_.resize(default_buffer_size);
328
1/1
✓ Branch 8 → 9 taken 2171 times.
2171 out_buf_.resize(default_buffer_size);
329 2171 }
330
331 2171 ~impl()
332 {
333
1/2
✓ Branch 2 → 3 taken 2171 times.
✗ Branch 2 → 4 not taken.
2171 if (ext_bio_)
334 2171 BIO_free(ext_bio_);
335
1/2
✓ Branch 4 → 5 taken 2171 times.
✗ Branch 4 → 6 not taken.
2171 if (ssl_)
336 2171 SSL_free(ssl_);
337 2171 }
338
339 20 void reset()
340 {
341
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 20 times.
20 if (!ssl_)
342 return;
343
344 // Preserves SSL* and BIO pair, releases session state
345
1/1
✓ Branch 4 → 5 taken 20 times.
20 SSL_clear(ssl_);
346
347 // Drain stale data from the external BIO
348 char drain[1024];
349
2/3
✓ Branch 7 → 8 taken 20 times.
✗ Branch 8 → 6 not taken.
✓ Branch 8 → 9 taken 20 times.
20 while (BIO_ctrl_pending(ext_bio_) > 0)
350 BIO_read(ext_bio_, drain, sizeof(drain));
351
352 // SSL_clear clears per-session settings; reapply hostname
353 20 auto& cd = detail::get_tls_context_data(ctx_);
354
1/1
✓ Branch 10 → 11 taken 20 times.
20 apply_hostname_verification(ssl_, cd.hostname);
355
356 20 used_ = false;
357 }
358
359
1/1
✓ Branch 5 → 6 taken 68149 times.
68149 capy::task<std::error_code> flush_output()
360 {
361 while (BIO_ctrl_pending(ext_bio_) > 0)
362 {
363 std::size_t got = 0;
364 while (BIO_ctrl_pending(ext_bio_) > 0 && got < out_buf_.size())
365 {
366 int put = static_cast<int>(BIO_ctrl_pending(ext_bio_));
367 put = (std::min)(put, static_cast<int>(out_buf_.size() - got));
368 int r = BIO_read(ext_bio_, out_buf_.data() + got, put);
369 if (r <= 0)
370 break;
371 got += static_cast<std::size_t>(r);
372 }
373 if (got == 0)
374 break;
375
376 {
377 auto [lec, guard] = co_await io_cm_.scoped_lock();
378 if (lec)
379 co_return lec;
380 auto [ec, n] = co_await capy::write(
381 s_, capy::const_buffer(out_buf_.data(), got));
382 if (ec)
383 co_return ec;
384 }
385 }
386 co_return std::error_code{};
387 136298 }
388
389
1/1
✓ Branch 5 → 6 taken 34649 times.
34649 capy::task<std::error_code> read_input()
390 {
391 auto [lec, guard] = co_await io_cm_.scoped_lock();
392 if (lec)
393 co_return lec;
394 auto [ec, n] = co_await s_.read_some(
395 capy::mutable_buffer(in_buf_.data(), in_buf_.size()));
396 if (ec)
397 co_return ec;
398
399 int got = BIO_write(ext_bio_, in_buf_.data(), static_cast<int>(n));
400 if (got < static_cast<int>(n))
401 {
402 co_return make_error_code(std::errc::no_buffer_space);
403 }
404
405 co_return std::error_code{};
406 69298 }
407
408 capy::io_task<std::size_t>
409
1/1
✓ Branch 6 → 7 taken 32732 times.
32732 do_read_some(capy::mutable_buffer_array<capy::detail::max_iovec_> buffers)
410 {
411 std::error_code ec;
412 std::size_t total_read = 0;
413
414 for (auto& buf : buffers)
415 {
416 char* dest = static_cast<char*>(buf.data());
417 int remaining = static_cast<int>(buf.size());
418
419 while (remaining > 0)
420 {
421 ERR_clear_error();
422 int ret = SSL_read(ssl_, dest, remaining);
423
424 if (ret > 0)
425 {
426 dest += ret;
427 remaining -= ret;
428 total_read += static_cast<std::size_t>(ret);
429
430 if (total_read > 0)
431 co_return {std::error_code{}, total_read};
432 }
433 else
434 {
435 int err = SSL_get_error(ssl_, ret);
436
437 if (err == SSL_ERROR_WANT_WRITE)
438 {
439 ec = co_await flush_output();
440 if (ec)
441 co_return {ec, total_read};
442 }
443 else if (err == SSL_ERROR_WANT_READ)
444 {
445 ec = co_await flush_output();
446 if (ec)
447 co_return {ec, total_read};
448
449 ec = co_await read_input();
450 if (ec)
451 {
452 if (ec == make_error_code(capy::error::eof))
453 {
454 if (SSL_get_shutdown(ssl_) &
455 SSL_RECEIVED_SHUTDOWN)
456 ec = make_error_code(capy::error::eof);
457 else
458 ec = make_error_code(
459 capy::error::stream_truncated);
460 }
461 co_return {ec, total_read};
462 }
463 }
464 else if (err == SSL_ERROR_ZERO_RETURN)
465 {
466 co_return {
467 make_error_code(capy::error::eof), total_read};
468 }
469 else if (err == SSL_ERROR_SYSCALL)
470 {
471 unsigned long ssl_err = ERR_get_error();
472 if (ssl_err == 0)
473 ec = make_error_code(capy::error::stream_truncated);
474 else
475 ec = std::error_code(
476 static_cast<int>(ssl_err),
477 std::system_category());
478 co_return {ec, total_read};
479 }
480 else
481 {
482 unsigned long ssl_err = ERR_get_error();
483 ec = std::error_code(
484 static_cast<int>(ssl_err), std::system_category());
485 co_return {ec, total_read};
486 }
487 }
488 }
489 }
490
491 co_return {std::error_code{}, total_read};
492 65464 }
493
494 capy::io_task<std::size_t>
495
1/1
✓ Branch 6 → 7 taken 32709 times.
32709 do_write_some(capy::const_buffer_array<capy::detail::max_iovec_> buffers)
496 {
497 std::error_code ec;
498 std::size_t total_written = 0;
499
500 for (auto const& buf : buffers)
501 {
502 char const* src = static_cast<char const*>(buf.data());
503 int remaining = static_cast<int>(buf.size());
504
505 while (remaining > 0)
506 {
507 ERR_clear_error();
508 int ret = SSL_write(ssl_, src, remaining);
509
510 if (ret > 0)
511 {
512 src += ret;
513 remaining -= ret;
514 total_written += static_cast<std::size_t>(ret);
515
516 if (total_written > 0)
517 {
518 ec = co_await flush_output();
519 co_return {ec, total_written};
520 }
521 }
522 else
523 {
524 int err = SSL_get_error(ssl_, ret);
525
526 if (err == SSL_ERROR_WANT_WRITE)
527 {
528 ec = co_await flush_output();
529 if (ec)
530 co_return {ec, total_written};
531 }
532 else if (err == SSL_ERROR_WANT_READ)
533 {
534 ec = co_await flush_output();
535 if (ec)
536 co_return {ec, total_written};
537
538 ec = co_await read_input();
539 if (ec)
540 co_return {ec, total_written};
541 }
542 else
543 {
544 unsigned long ssl_err = ERR_get_error();
545 ec = std::error_code(
546 static_cast<int>(ssl_err), std::system_category());
547 co_return {ec, total_written};
548 }
549 }
550 }
551 }
552
553 co_return {std::error_code{}, total_written};
554 65418 }
555
556
1/1
✓ Branch 5 → 6 taken 1482 times.
1482 capy::io_task<> do_handshake(int type)
557 {
558 if (used_)
559 reset();
560
561 std::error_code ec;
562
563 while (true)
564 {
565 ERR_clear_error();
566 int ret;
567 if (type == openssl_stream::client)
568 ret = SSL_connect(ssl_);
569 else
570 ret = SSL_accept(ssl_);
571
572 if (ret == 1)
573 {
574 used_ = true;
575 ec = co_await flush_output();
576 co_return {ec};
577 }
578 else
579 {
580 int err = SSL_get_error(ssl_, ret);
581
582 if (err == SSL_ERROR_WANT_WRITE)
583 {
584 ec = co_await flush_output();
585 if (ec)
586 co_return {ec};
587 }
588 else if (err == SSL_ERROR_WANT_READ)
589 {
590 ec = co_await flush_output();
591 if (ec)
592 co_return {ec};
593
594 ec = co_await read_input();
595 if (ec)
596 co_return {ec};
597 }
598 else
599 {
600 unsigned long ssl_err = ERR_get_error();
601 ec = std::error_code(
602 static_cast<int>(ssl_err), std::system_category());
603 co_return {ec};
604 }
605 }
606 }
607 2964 }
608
609
1/1
✓ Branch 5 → 6 taken 36 times.
36 capy::io_task<> do_shutdown()
610 {
611 std::error_code ec;
612
613 while (true)
614 {
615 ERR_clear_error();
616 int ret = SSL_shutdown(ssl_);
617
618 if (ret == 1)
619 {
620 ec = co_await flush_output();
621 co_return {ec};
622 }
623 else if (ret == 0)
624 {
625 ec = co_await flush_output();
626 if (ec)
627 co_return {ec};
628
629 ec = co_await read_input();
630 if (ec)
631 {
632 ec = normalize_openssl_shutdown_read_error(ec);
633 co_return {ec};
634 }
635 }
636 else
637 {
638 int err = SSL_get_error(ssl_, ret);
639
640 if (err == SSL_ERROR_WANT_WRITE)
641 {
642 ec = co_await flush_output();
643 if (ec)
644 co_return {ec};
645 }
646 else if (err == SSL_ERROR_WANT_READ)
647 {
648 ec = co_await flush_output();
649 if (ec)
650 co_return {ec};
651
652 ec = co_await read_input();
653 if (ec)
654 {
655 ec = normalize_openssl_shutdown_read_error(ec);
656 co_return {ec};
657 }
658 }
659 else
660 {
661 unsigned long ssl_err = ERR_get_error();
662 if (ssl_err == 0 && err == SSL_ERROR_SYSCALL)
663 {
664 ec = {};
665 }
666 else
667 {
668 ec = std::error_code(
669 static_cast<int>(ssl_err), std::system_category());
670 }
671 co_return {ec};
672 }
673 }
674 }
675 72 }
676
677 2171 std::error_code init_ssl()
678 {
679 2171 auto& cd = detail::get_tls_context_data(ctx_);
680
1/1
✓ Branch 3 → 4 taken 2171 times.
2171 SSL_CTX* native_ctx = detail::get_openssl_context(cd);
681
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 2171 times.
2171 if (!native_ctx)
682 {
683 unsigned long err = ERR_get_error();
684 return std::error_code(
685 static_cast<int>(err), std::system_category());
686 }
687
688
1/1
✓ Branch 7 → 8 taken 2171 times.
2171 ssl_ = SSL_new(native_ctx);
689
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 11 taken 2171 times.
2171 if (!ssl_)
690 {
691 unsigned long err = ERR_get_error();
692 return std::error_code(
693 static_cast<int>(err), std::system_category());
694 }
695
696 2171 BIO* int_bio = nullptr;
697
2/3
✓ Branch 11 → 12 taken 2171 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 16 taken 2171 times.
2171 if (!BIO_new_bio_pair(&int_bio, 0, &ext_bio_, 0))
698 {
699 unsigned long err = ERR_get_error();
700 SSL_free(ssl_);
701 ssl_ = nullptr;
702 return std::error_code(
703 static_cast<int>(err), std::system_category());
704 }
705
706
1/1
✓ Branch 16 → 17 taken 2171 times.
2171 SSL_set_bio(ssl_, int_bio, int_bio);
707
708
1/1
✓ Branch 17 → 18 taken 2171 times.
2171 apply_hostname_verification(ssl_, cd.hostname);
709
710 2171 return {};
711 }
712 };
713
714 openssl_stream::impl*
715 2171 openssl_stream::make_impl(capy::any_stream& stream, tls_context const& ctx)
716 {
717
2/4
✓ Branch 2 → 3 taken 2171 times.
✓ Branch 4 → 5 taken 2171 times.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 21 not taken.
2171 auto* p = new impl(stream, ctx);
718
719
1/1
✓ Branch 6 → 7 taken 2171 times.
2171 auto ec = p->init_ssl();
720
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 13 taken 2171 times.
2171 if (ec)
721 {
722 delete p;
723 return nullptr;
724 }
725
726 2171 return p;
727 }
728
729 2171 openssl_stream::~openssl_stream()
730 {
731
1/2
✓ Branch 2 → 3 taken 2171 times.
✗ Branch 2 → 5 not taken.
2171 delete impl_;
732 2171 }
733
734 openssl_stream::openssl_stream(openssl_stream&& other) noexcept
735 : stream_(std::move(other.stream_))
736 , impl_(other.impl_)
737 {
738 other.impl_ = nullptr;
739 }
740
741 openssl_stream&
742 openssl_stream::operator=(openssl_stream&& other) noexcept
743 {
744 if (this != &other)
745 {
746 delete impl_;
747 stream_ = std::move(other.stream_);
748 impl_ = other.impl_;
749 other.impl_ = nullptr;
750 }
751 return *this;
752 }
753
754 capy::io_task<std::size_t>
755
1/1
✓ Branch 6 → 7 taken 32732 times.
32732 openssl_stream::do_read_some(
756 capy::mutable_buffer_array<capy::detail::max_iovec_> buffers)
757 {
758 co_return co_await impl_->do_read_some(buffers);
759 65464 }
760
761 capy::io_task<std::size_t>
762
1/1
✓ Branch 6 → 7 taken 32709 times.
32709 openssl_stream::do_write_some(
763 capy::const_buffer_array<capy::detail::max_iovec_> buffers)
764 {
765 co_return co_await impl_->do_write_some(buffers);
766 65418 }
767
768 capy::io_task<>
769
1/1
✓ Branch 5 → 6 taken 1482 times.
1482 openssl_stream::handshake(handshake_type type)
770 {
771 co_return co_await impl_->do_handshake(type);
772 2964 }
773
774 capy::io_task<>
775
1/1
✓ Branch 5 → 6 taken 36 times.
36 openssl_stream::shutdown()
776 {
777 co_return co_await impl_->do_shutdown();
778 72 }
779
780 void
781 16 openssl_stream::reset()
782 {
783 16 impl_->reset();
784 16 }
785
786 std::string_view
787 1 openssl_stream::name() const noexcept
788 {
789 1 return "openssl";
790 }
791
792 } // namespace boost::corosio
793