src/openssl/src/detail/engine.cpp

87.1% Lines (357/410) 100.0% List of functions (34/34) 79.8% Branches (352/441)
engine.cpp
f(x) Functions (34)
Function Calls Lines Branches Blocks
boost::corosio::(anonymous namespace)::tls_method_compat() :33 2888x 100.0% 100.0% boost::corosio::(anonymous namespace)::apply_hostname_verification(ssl_st*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) :43 2928x 78.6% 81.2% 88.9% boost::corosio::(anonymous namespace)::openssl_proto_version(boost::corosio::tls_version) :73 5776x 100.0% 100.0% 100.0% boost::corosio::(anonymous namespace)::build_alpn_wire(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&) :82 4x 77.8% 70.0% 82.6% boost::corosio::(anonymous namespace)::openssl_category_impl::name() const :98 1x 100.0% 100.0% boost::corosio::(anonymous namespace)::openssl_category_impl::message(int) const :104 2x 100.0% 100.0% 72.7% boost::corosio::(anonymous namespace)::make_openssl_error(unsigned long) :117 30x 75.0% 50.0% 71.4% boost::corosio::(anonymous namespace)::map_openssl_fatal() :132 29x 80.0% 50.0% 80.0% boost::corosio::openssl_category() :143 37x 100.0% 75.0% 100.0% boost::corosio::detail::password_callback(char*, int, int, void*) :158 2x 92.3% 66.7% 87.5% boost::corosio::detail::verify_callback_trampoline(int, x509_store_ctx_st*) :185 10x 82.6% 60.7% 82.4% boost::corosio::detail::alpn_select_cb(ssl_st*, unsigned char const**, unsigned char*, unsigned char const*, unsigned int, void*) :244 2x 87.5% 72.2% 89.3% boost::corosio::detail::sni_callback(ssl_st*, int*, void*) :278 12x 100.0% 72.7% 94.4% boost::corosio::detail::openssl_native_context::openssl_native_context(boost::corosio::detail::tls_context_data const&) :319 2888x 88.5% 87.4% 88.3% boost::corosio::detail::openssl_native_context::~openssl_native_context() :641 5776x 100.0% 100.0% boost::corosio::detail::get_openssl_native_context(boost::corosio::detail::tls_context_data const&) :649 2893x 100.0% 100.0% boost::corosio::detail::get_openssl_native_context(boost::corosio::detail::tls_context_data const&)::{lambda()#1}::operator()() const :652 2888x 100.0% 50.0% 71.4% boost::corosio::detail::openssl::engine::~engine() :662 4215x 100.0% 100.0% 100.0% boost::corosio::detail::openssl::engine::init(boost::corosio::tls_context const&) :671 2893x 58.8% 53.8% 66.7% boost::corosio::detail::openssl::engine::reset() :708 65x 50.0% 50.0% 64.7% boost::corosio::detail::openssl::engine::context_setup_failed() const :739 5805x 100.0% 100.0% 100.0% boost::corosio::detail::openssl::engine::check_context() const :747 5805x 100.0% 100.0% 100.0% boost::corosio::detail::openssl::engine::check_session() const :755 2934x 75.0% 50.0% 75.0% boost::corosio::detail::openssl::engine::prepare(boost::corosio::tls_context const&, boost::corosio::tls_role, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) :763 2934x 76.9% 78.9% 80.6% boost::corosio::detail::openssl::engine::apply_hostname(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) :800 2928x 100.0% 100.0% boost::corosio::detail::openssl::engine::apply_alpn_offer() :806 2122x 100.0% 100.0% 100.0% boost::corosio::detail::openssl::engine::capture_alpn(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) const :822 1268x 100.0% 83.3% 100.0% boost::corosio::detail::openssl::engine::perform(boost::corosio::detail::engine_op, void*, unsigned long long) :832 147456x 93.6% 86.4% 89.6% boost::corosio::detail::openssl::engine::put_input(unsigned char const*, unsigned long long) :936 59x 100.0% 100.0% boost::corosio::detail::openssl::engine::input_area() :943 50215x 83.3% 60.0% 80.0% boost::corosio::detail::openssl::engine::input_committed(unsigned long long) :957 48639x 80.0% 66.7% 83.3% boost::corosio::detail::openssl::engine::pending_output() const :968 349603x 100.0% 100.0% boost::corosio::detail::openssl::engine::get_output(unsigned char*, unsigned long long) :974 50713x 100.0% 100.0% boost::corosio::detail::openssl::engine::received_shutdown() const :981 1682x 100.0% 100.0%
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Steve Gerbino
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 "engine.hpp"
11
12 // openssl_category is declared alongside the public stream class
13 #include <boost/corosio/openssl_stream.hpp>
14 #include <boost/capy/error.hpp>
15
16 // Internal context implementation
17 #include "src/tls/detail/context_impl.hpp"
18
19 #include <openssl/ssl.h>
20 #include <openssl/err.h>
21 #include <openssl/bio.h>
22 #include <openssl/x509.h>
23 #include <openssl/pkcs12.h>
24
25 #include <cstring>
26 #include <vector>
27
28 namespace boost::corosio {
29
30 namespace {
31
32 inline SSL_METHOD const*
33 2888x tls_method_compat() noexcept
34 {
35 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
36 2888x return TLS_method();
37 #else
38 return SSLv23_method();
39 #endif
40 }
41
42 inline bool
43 2928x apply_hostname_verification(SSL* ssl, std::string const& hostname)
44 {
45 // SSL_clear retains a previously applied name; an empty hostname
46 // must clear SNI and the verify-param host or a reset stream
47 // would leak the old peer's name into the next handshake
48
2/2
✓ Branch 3 → 4 taken 2915 times.
✓ Branch 3 → 5 taken 13 times.
2928x char const* name = hostname.empty() ? nullptr : hostname.c_str();
49
50 // RFC 6066 excludes IP literals from SNI, and a literal must match
51 // the certificate's iPAddress entries rather than its DNS names.
52 // The unused field is cleared so a reset stream cannot carry the
53 // previous target's matching rule.
54
4/4
✓ Branch 6 → 7 taken 13 times.
✓ Branch 6 → 10 taken 2915 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 11 times.
2928x bool const is_ip = name && detail::is_ip_literal(hostname);
55
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 13 taken 2926 times.
2928x char const* dns_name = is_ip ? nullptr : name;
56
57
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 2928 times.
2928x if (SSL_set_tlsext_host_name(ssl, dns_name) != 1)
58 return false;
59
60 2928x auto* param = SSL_get0_param(ssl);
61
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 2928 times.
2928x if (!param)
62 return name == nullptr;
63
64
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 2928 times.
2928x if (X509_VERIFY_PARAM_set1_host(param, dns_name, 0) != 1)
65 return false;
66
2/2
✓ Branch 23 → 24 taken 2 times.
✓ Branch 23 → 26 taken 2926 times.
2928x if (is_ip)
67 2x return X509_VERIFY_PARAM_set1_ip_asc(param, name) == 1;
68 2926x return X509_VERIFY_PARAM_set1_ip(param, nullptr, 0) == 1;
69 }
70
71 // Map a portable protocol version to the OpenSSL version constant.
72 inline int
73 5776x openssl_proto_version(tls_version v) noexcept
74 {
75
2/2
✓ Branch 2 → 3 taken 2893 times.
✓ Branch 2 → 4 taken 2883 times.
5776x return v == tls_version::tls_1_3 ? TLS1_3_VERSION : TLS1_2_VERSION;
76 }
77
78 // Encode a protocol list into ALPN wire format: each entry is a
79 // one-byte length followed by that many bytes. Entries longer than 255
80 // bytes are skipped (invalid per RFC 7301).
81 inline std::string
82 4x build_alpn_wire(std::vector<std::string> const& protocols)
83 {
84 4x std::string wire;
85
2/2
✓ Branch 19 → 5 taken 6 times.
✓ Branch 19 → 20 taken 4 times.
10x for (auto const& p : protocols)
86 {
87
3/6
✓ Branch 7 → 8 taken 6 times.
✗ Branch 7 → 10 not taken.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 6 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 6 times.
6x if (p.empty() || p.size() > 255)
88 continue;
89
1/1
✓ Branch 15 → 16 taken 6 times.
6x wire.push_back(static_cast<char>(p.size()));
90
1/1
✓ Branch 16 → 17 taken 6 times.
6x wire.append(p);
91 }
92 4x return wire;
93 }
94
95 class openssl_category_impl final : public std::error_category
96 {
97 char const*
98 1x name() const noexcept override
99 {
100 1x return "corosio.openssl";
101 }
102
103 std::string
104 2x message(int value) const override
105 {
106 char buf[256];
107
1/1
✓ Branch 2 → 3 taken 2 times.
2x ::ERR_error_string_n(
108 static_cast<unsigned long>(value), buf, sizeof(buf));
109
1/1
✓ Branch 5 → 6 taken 2 times.
4x return buf;
110 }
111 };
112
113 // Convert a packed OpenSSL error (from ERR_get_error) into an error_code.
114 // Codes from the ERR_LIB_SYS library carry a genuine errno reason and are
115 // reported with the system category; everything else uses openssl_category.
116 inline std::error_code
117 30x make_openssl_error(unsigned long err) noexcept
118 {
119
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 30 times.
30x if (ERR_GET_LIB(err) == ERR_LIB_SYS)
120 return std::error_code(
121 static_cast<int>(ERR_GET_REASON(err)), std::system_category());
122 30x return std::error_code(static_cast<int>(err), openssl_category());
123 }
124
125 // Maps a terminal engine failure (any path that isn't WANT_READ/WANT_WRITE/
126 // ZERO_RETURN) to an error_code. SYSCALL and a bare SSL_ERROR_SSL both leave
127 // the queue empty when the transport simply vanished rather than reporting
128 // a protocol failure; make_openssl_error(0) is falsy, which would otherwise
129 // turn that vanished transport into a false success, so an empty queue maps
130 // to stream_truncated instead.
131 inline std::error_code
132 29x map_openssl_fatal() noexcept
133 {
134 29x unsigned long ssl_err = ERR_get_error();
135
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 29 times.
29x if (ssl_err == 0)
136 return make_error_code(capy::error::stream_truncated);
137 29x return make_openssl_error(ssl_err);
138 }
139
140 } // namespace
141
142 std::error_category const&
143 37x openssl_category() noexcept
144 {
145
3/4
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 7 taken 35 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 7 not taken.
37x static openssl_category_impl instance;
146 37x return instance;
147 }
148
149 //
150 // Native context caching
151 //
152
153 namespace detail {
154
155 static int sni_ctx_data_index = -1;
156
157 static int
158 2x password_callback(char* buf, int size, int rwflag, void* userdata)
159 {
160 2x auto* cd = static_cast<tls_context_data const*>(userdata);
161
3/6
✓ Branch 2 → 3 taken 2 times.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 2 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 2 times.
2x if (!cd || !cd->password_callback)
162 return 0;
163
164 2x tls_password_purpose purpose = (rwflag == 0)
165 2x ? tls_password_purpose::for_reading
166 : tls_password_purpose::for_writing;
167
168 std::string password =
169
1/1
✓ Branch 9 → 10 taken 2 times.
2x cd->password_callback(static_cast<std::size_t>(size), purpose);
170
171 2x int len = static_cast<int>(password.size());
172
2/2
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 1 time.
2x if (len > size)
173 1x len = size;
174
175 2x std::memcpy(buf, password.data(), static_cast<std::size_t>(len));
176 2x return len;
177 2x }
178
179 // Trampoline installed via SSL_CTX_set_verify. Recovers the portable
180 // context data from the SSL_CTX ex_data (populated for every context)
181 // and applies, in order: the revocation policy's soft-fail downgrade,
182 // then the user's verify callback. Installed whenever a verify callback
183 // or a non-disabled revocation policy is configured.
184 static int
185 10x verify_callback_trampoline(int preverified, X509_STORE_CTX* store_ctx)
186 {
187 10x SSL* ssl = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(
188 store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
189
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 10 times.
10x if (!ssl)
190 return preverified;
191
192 auto* cd = static_cast<tls_context_data const*>(
193 10x SSL_CTX_get_ex_data(SSL_get_SSL_CTX(ssl), sni_ctx_data_index));
194
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 10 times.
10x if (!cd)
195 return preverified;
196
197 10x bool ok = preverified != 0;
198
199 // Soft-fail revocation: accept certificates whose revocation status
200 // could not be determined (missing/expired CRL), but never downgrade
201 // an actual revocation. hard_fail leaves every CRL error fatal.
202
4/4
✓ Branch 10 → 11 taken 4 times.
✓ Branch 10 → 18 taken 6 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 18 taken 3 times.
10x if (!ok && cd->revocation == tls_revocation_policy::soft_fail)
203 {
204 1x int const err = X509_STORE_CTX_get_error(store_ctx);
205
1/4
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 17 taken 1 time.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 17 not taken.
1x if (err == X509_V_ERR_UNABLE_TO_GET_CRL ||
206 err == X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER ||
207 err == X509_V_ERR_CRL_HAS_EXPIRED ||
208 err == X509_V_ERR_CRL_NOT_YET_VALID)
209 1x ok = true;
210 }
211
212
2/2
✓ Branch 19 → 20 taken 6 times.
✓ Branch 19 → 30 taken 4 times.
10x if (cd->verify_callback)
213 {
214 // Expose the current certificate's DER so the callback can inspect
215 // it portably. i2d_X509 allocates; free it after the callback.
216
1/1
✓ Branch 20 → 21 taken 6 times.
6x X509* cert = X509_STORE_CTX_get_current_cert(store_ctx);
217 6x unsigned char* der = nullptr;
218
2/3
✓ Branch 21 → 22 taken 6 times.
✗ Branch 21 → 24 not taken.
✓ Branch 22 → 23 taken 6 times.
6x int der_len = cert ? i2d_X509(cert, &der) : 0;
219
220 verify_context vc(
221 store_ctx, der,
222 6x der_len > 0 ? static_cast<std::size_t>(der_len) : 0);
223
1/1
✓ Branch 26 → 27 taken 6 times.
6x ok = cd->verify_callback(ok, vc);
224
225
1/2
✓ Branch 27 → 28 taken 6 times.
✗ Branch 27 → 29 not taken.
6x if (der)
226
1/1
✓ Branch 28 → 29 taken 6 times.
6x OPENSSL_free(der);
227 }
228
229
2/2
✓ Branch 30 → 31 taken 6 times.
✓ Branch 30 → 32 taken 4 times.
10x return ok ? 1 : 0;
230 }
231
232 // Server-side ALPN selection. Chooses the server's most-preferred
233 // protocol that the client also offered. On no overlap it sends a fatal
234 // no_application_protocol alert (RFC 7301 §3.2).
235 //
236 // `arg` points at the native context's build-time snapshot of the server
237 // preference list (a std::vector<std::string>), so client offer and server
238 // selection are both taken from the same immutable snapshot.
239 //
240 // The selected protocol pointer must stay valid until the callback runs
241 // again, so we point *out into the client list `in` (OpenSSL keeps it
242 // valid for the connection) rather than into a local buffer.
243 static int
244 2x alpn_select_cb(
245 SSL* /* ssl */, unsigned char const** out, unsigned char* outlen,
246 unsigned char const* in, unsigned int inlen, void* arg)
247 {
248 2x auto const* prefs = static_cast<std::vector<std::string> const*>(arg);
249
3/6
✓ Branch 2 → 3 taken 2 times.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 2 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 2 times.
2x if (!prefs || prefs->empty())
250 return SSL_TLSEXT_ERR_NOACK; // nothing configured (defensive)
251
252 // Server preference order wins: for each server protocol, look for a
253 // matching entry in the client's offered list.
254
2/2
✓ Branch 27 → 11 taken 2 times.
✓ Branch 27 → 28 taken 1 time.
3x for (auto const& pref : *prefs)
255 {
256
2/2
✓ Branch 24 → 13 taken 2 times.
✓ Branch 24 → 25 taken 1 time.
3x for (unsigned int i = 0; i + 1 <= inlen;)
257 {
258 2x unsigned int len = in[i];
259
1/2
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 2 times.
2x if (i + 1 + len > inlen)
260 break; // malformed
261
4/4
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 20 taken 1 time.
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 23 taken 1 time.
3x if (len == pref.size() &&
262
1/2
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 20 not taken.
1x std::memcmp(in + i + 1, pref.data(), len) == 0)
263 {
264 1x *out = in + i + 1;
265 1x *outlen = static_cast<unsigned char>(len);
266 1x return SSL_TLSEXT_ERR_OK;
267 }
268 1x i += 1 + len;
269 }
270 }
271
272 // The server supports ALPN but shares no protocol with the client.
273 // RFC 7301 §3.2: fail the handshake with a fatal alert.
274 1x return SSL_TLSEXT_ERR_ALERT_FATAL;
275 }
276
277 static int
278 12x sni_callback(SSL* ssl, int* /* alert */, void* /* arg */)
279 {
280 12x char const* servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
281
2/2
✓ Branch 3 → 4 taken 4 times.
✓ Branch 3 → 5 taken 8 times.
12x if (!servername)
282 4x return SSL_TLSEXT_ERR_NOACK;
283
284 8x SSL_CTX* ctx = SSL_get_SSL_CTX(ssl);
285 auto* cd = static_cast<tls_context_data const*>(
286 8x SSL_CTX_get_ex_data(ctx, sni_ctx_data_index));
287
288
3/6
✓ Branch 7 → 8 taken 8 times.
✗ Branch 7 → 11 not taken.
✓ Branch 9 → 10 taken 8 times.
✗ Branch 9 → 11 not taken.
✓ Branch 12 → 13 taken 8 times.
✗ Branch 12 → 17 not taken.
8x if (cd && cd->servername_callback)
289 {
290
3/3
✓ Branch 14 → 15 taken 8 times.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 7 times.
8x if (!cd->servername_callback(servername))
291 1x return SSL_TLSEXT_ERR_ALERT_FATAL;
292 }
293
294 7x return SSL_TLSEXT_ERR_OK;
295 }
296
297 class openssl_native_context : public native_context_base
298 {
299 public:
300 SSL_CTX* ctx_;
301 tls_context_data const* cd_;
302 // Set when a requested configuration could not be applied: an inverted
303 // protocol window (min > max), a cipher list / suite the library
304 // rejected, a protocol-version bound that would not set, or a CRL that
305 // parsed as neither PEM nor DER. Silently proceeding would negotiate an
306 // unexpected version, ignore the requested ciphers, or weaken revocation
307 // (fail-open under soft_fail), so do_handshake refuses the handshake.
308 bool setup_failed_ = false;
309 // ALPN offer in wire format (length-prefixed), encoded once from the
310 // immutable protocol list. The client sets it per-SSL each handshake;
311 // caching it here avoids re-encoding and re-allocating per connection.
312 std::string alpn_wire_;
313 // Server preference snapshot, captured at build time so the select
314 // callback matches against the same immutable list the client offers
315 // from (see alpn_select_cb). Its address is handed to OpenSSL as the
316 // callback arg, so it must outlive the SSL_CTX (it does — same object).
317 std::vector<std::string> alpn_snapshot_;
318
319 2888x explicit openssl_native_context(tls_context_data const& cd)
320 5776x : ctx_(nullptr)
321 2888x , cd_(&cd)
322 {
323
1/1
✓ Branch 6 → 7 taken 2888 times.
2888x ctx_ = SSL_CTX_new(tls_method_compat());
324
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 2888 times.
2888x if (!ctx_)
325 return;
326
327
2/2
✓ Branch 9 → 10 taken 5 times.
✓ Branch 9 → 12 taken 2883 times.
2888x if (sni_ctx_data_index < 0)
328 5x sni_ctx_data_index =
329
1/1
✓ Branch 10 → 11 taken 5 times.
5x SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
330
331
1/1
✓ Branch 12 → 13 taken 2888 times.
2888x SSL_CTX_set_ex_data(
332 ctx_, sni_ctx_data_index, const_cast<tls_context_data*>(&cd));
333
334
2/2
✓ Branch 14 → 15 taken 8 times.
✓ Branch 14 → 16 taken 2880 times.
2888x if (cd.servername_callback)
335
1/1
✓ Branch 15 → 16 taken 8 times.
8x SSL_CTX_set_tlsext_servername_callback(ctx_, sni_callback);
336
337 // ALPN server-side selection. The callback only fires when this
338 // context is used as a server; the client offer (encoded once here)
339 // is set per-SSL from alpn_wire_. Snapshot the preference list so the
340 // callback and the client offer share one immutable source.
341
2/2
✓ Branch 17 → 18 taken 4 times.
✓ Branch 17 → 24 taken 2884 times.
2888x if (!cd.alpn_protocols.empty())
342 {
343
1/1
✓ Branch 18 → 19 taken 4 times.
4x alpn_snapshot_ = cd.alpn_protocols;
344
1/1
✓ Branch 19 → 20 taken 4 times.
4x SSL_CTX_set_alpn_select_cb(ctx_, alpn_select_cb, &alpn_snapshot_);
345
1/1
✓ Branch 20 → 21 taken 4 times.
4x alpn_wire_ = build_alpn_wire(cd.alpn_protocols);
346 }
347
348
1/1
✓ Branch 24 → 25 taken 2888 times.
2888x SSL_CTX_set_mode(ctx_, SSL_MODE_ENABLE_PARTIAL_WRITE);
349
1/1
✓ Branch 25 → 26 taken 2888 times.
2888x SSL_CTX_set_mode(ctx_, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
350 #if defined(SSL_MODE_RELEASE_BUFFERS)
351
1/1
✓ Branch 26 → 27 taken 2888 times.
2888x SSL_CTX_set_mode(ctx_, SSL_MODE_RELEASE_BUFFERS);
352 #endif
353
354 // Enforce the configured protocol version window (role-agnostic).
355 // An inverted window (min > max) admits no protocol; fail closed
356 // rather than silently negotiate an unexpected version.
357
2/2
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 29 taken 2887 times.
2888x if (cd.min_version > cd.max_version)
358 1x setup_failed_ = true;
359
2/3
✓ Branch 30 → 31 taken 2888 times.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 2888 times.
2888x if (!SSL_CTX_set_min_proto_version(
360 ctx_, openssl_proto_version(cd.min_version)))
361 setup_failed_ = true;
362
2/3
✓ Branch 34 → 35 taken 2888 times.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 37 taken 2888 times.
2888x if (!SSL_CTX_set_max_proto_version(
363 ctx_, openssl_proto_version(cd.max_version)))
364 setup_failed_ = true;
365
366 2888x int verify_mode_flag = SSL_VERIFY_NONE;
367
2/2
✓ Branch 37 → 38 taken 2099 times.
✓ Branch 37 → 39 taken 789 times.
2888x if (cd.verification_mode == tls_verify_mode::peer)
368 2099x verify_mode_flag = SSL_VERIFY_PEER;
369
2/2
✓ Branch 39 → 40 taken 3 times.
✓ Branch 39 → 41 taken 786 times.
789x else if (cd.verification_mode == tls_verify_mode::require_peer)
370 3x verify_mode_flag =
371 SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
372 // The trampoline runs the revocation soft-fail downgrade and the
373 // user callback, so install it if either is configured.
374 bool const need_trampoline =
375
2/2
✓ Branch 42 → 43 taken 2883 times.
✓ Branch 42 → 44 taken 5 times.
5771x cd.verify_callback ||
376
2/2
✓ Branch 43 → 44 taken 3 times.
✓ Branch 43 → 45 taken 2880 times.
5771x cd.revocation != tls_revocation_policy::disabled;
377
3/3
✓ Branch 46 → 47 taken 8 times.
✓ Branch 46 → 48 taken 2880 times.
✓ Branch 49 → 50 taken 2888 times.
2888x SSL_CTX_set_verify(
378 ctx_, verify_mode_flag,
379 need_trampoline ? &verify_callback_trampoline : nullptr);
380
381 // PKCS#12 bundle: decode cert + key + chain directly into the
382 // context. This is an alternative credential source; the PEM/DER
383 // fields below are only consulted when no bundle is supplied.
384
2/2
✓ Branch 51 → 52 taken 5 times.
✓ Branch 51 → 95 taken 2883 times.
2888x if (!cd.pkcs12_data.empty())
385 {
386 // A bundle that fails to decode or parse (wrong passphrase,
387 // malformed) must not leave the context silently credential-less:
388 // a client using PKCS#12 for mTLS would then fail open against a
389 // verify_mode::peer server. Fail closed like every other setup
390 // error.
391
1/1
✓ Branch 54 → 55 taken 5 times.
5x BIO* bio = BIO_new_mem_buf(
392 5x cd.pkcs12_data.data(),
393 5x static_cast<int>(cd.pkcs12_data.size()));
394
1/2
✗ Branch 55 → 56 not taken.
✓ Branch 55 → 57 taken 5 times.
5x if (!bio)
395 setup_failed_ = true;
396 else
397 {
398
1/1
✓ Branch 57 → 58 taken 5 times.
5x PKCS12* p12 = d2i_PKCS12_bio(bio, nullptr);
399
1/2
✗ Branch 58 → 59 not taken.
✓ Branch 58 → 60 taken 5 times.
5x if (!p12)
400 setup_failed_ = true;
401 else
402 {
403 5x EVP_PKEY* pkey = nullptr;
404 5x X509* cert = nullptr;
405 5x STACK_OF(X509)* chain = nullptr;
406
3/3
✓ Branch 61 → 62 taken 5 times.
✓ Branch 62 → 63 taken 3 times.
✓ Branch 62 → 84 taken 2 times.
5x if (PKCS12_parse(
407 p12, cd.pkcs12_password.c_str(), &pkey, &cert,
408 &chain))
409 {
410
1/2
✓ Branch 63 → 64 taken 3 times.
✗ Branch 63 → 65 not taken.
3x if (cert)
411
1/1
✓ Branch 64 → 65 taken 3 times.
3x SSL_CTX_use_certificate(ctx_, cert);
412
1/2
✓ Branch 65 → 66 taken 3 times.
✗ Branch 65 → 67 not taken.
3x if (pkey)
413
1/1
✓ Branch 66 → 67 taken 3 times.
3x SSL_CTX_use_PrivateKey(ctx_, pkey);
414
2/2
✓ Branch 67 → 68 taken 1 time.
✓ Branch 67 → 85 taken 2 times.
3x if (chain)
415
3/3
✓ Branch 82 → 83 taken 2 times.
✓ Branch 83 → 69 taken 1 time.
✓ Branch 83 → 85 taken 1 time.
2x for (int i = 0; i < sk_X509_num(chain); ++i)
416 {
417 // add_extra_chain_cert takes ownership of the
418 // dup only on success; free it (and fail
419 // closed) otherwise so a partial chain isn't
420 // sent silently.
421
2/2
✓ Branch 70 → 71 taken 1 time.
✓ Branch 71 → 72 taken 1 time.
1x X509* dup = X509_dup(sk_X509_value(chain, i));
422
2/4
✓ Branch 72 → 73 taken 1 time.
✗ Branch 72 → 75 not taken.
✗ Branch 77 → 78 not taken.
✓ Branch 77 → 80 taken 1 time.
2x if (!dup ||
423
2/3
✓ Branch 73 → 74 taken 1 time.
✗ Branch 74 → 75 not taken.
✓ Branch 74 → 76 taken 1 time.
1x !SSL_CTX_add_extra_chain_cert(ctx_, dup))
424 {
425 X509_free(dup);
426 setup_failed_ = true;
427 }
428 }
429 }
430 else
431 2x setup_failed_ = true;
432
1/1
✓ Branch 85 → 86 taken 5 times.
5x EVP_PKEY_free(pkey);
433
1/1
✓ Branch 86 → 87 taken 5 times.
5x X509_free(cert);
434
2/2
✓ Branch 87 → 88 taken 1 time.
✓ Branch 87 → 91 taken 4 times.
5x if (chain)
435
1/1
✓ Branch 90 → 91 taken 1 time.
1x sk_X509_pop_free(chain, X509_free);
436
1/1
✓ Branch 91 → 92 taken 5 times.
5x PKCS12_free(p12);
437 }
438
1/1
✓ Branch 93 → 94 taken 5 times.
5x ERR_clear_error();
439
1/1
✓ Branch 94 → 95 taken 5 times.
5x BIO_free(bio);
440 }
441 }
442
443
6/6
✓ Branch 96 → 97 taken 2883 times.
✓ Branch 96 → 100 taken 5 times.
✓ Branch 98 → 99 taken 784 times.
✓ Branch 98 → 100 taken 2099 times.
✓ Branch 101 → 102 taken 784 times.
✓ Branch 101 → 117 taken 2104 times.
2888x if (cd.pkcs12_data.empty() && !cd.entity_certificate.empty())
444 {
445 // An entity certificate that fails to parse must not pass
446 // silently: the handshake would run without the identity the
447 // caller configured and fail remotely instead of at setup.
448
1/1
✓ Branch 104 → 105 taken 784 times.
784x BIO* bio = BIO_new_mem_buf(
449 784x cd.entity_certificate.data(),
450 784x static_cast<int>(cd.entity_certificate.size()));
451
1/2
✓ Branch 105 → 106 taken 784 times.
✗ Branch 105 → 116 not taken.
784x if (bio)
452 {
453 784x X509* cert = nullptr;
454
2/2
✓ Branch 106 → 107 taken 782 times.
✓ Branch 106 → 109 taken 2 times.
784x if (cd.entity_cert_format == tls_file_format::pem)
455
1/1
✓ Branch 107 → 108 taken 782 times.
782x cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
456 else
457
1/1
✓ Branch 109 → 110 taken 2 times.
2x cert = d2i_X509_bio(bio, nullptr);
458
2/2
✓ Branch 111 → 112 taken 782 times.
✓ Branch 111 → 114 taken 2 times.
784x if (cert)
459 {
460
1/1
✓ Branch 112 → 113 taken 782 times.
782x SSL_CTX_use_certificate(ctx_, cert);
461
1/1
✓ Branch 113 → 115 taken 782 times.
782x X509_free(cert);
462 }
463 else
464 {
465 2x setup_failed_ = true;
466 }
467
1/1
✓ Branch 115 → 117 taken 784 times.
784x BIO_free(bio);
468 }
469 else
470 {
471 setup_failed_ = true;
472 }
473 }
474
475
6/6
✓ Branch 118 → 119 taken 2883 times.
✓ Branch 118 → 122 taken 5 times.
✓ Branch 120 → 121 taken 1 time.
✓ Branch 120 → 122 taken 2882 times.
✓ Branch 123 → 124 taken 1 time.
✓ Branch 123 → 138 taken 2887 times.
2888x if (cd.pkcs12_data.empty() && !cd.certificate_chain.empty())
476 {
477
1/1
✓ Branch 126 → 127 taken 1 time.
1x BIO* bio = BIO_new_mem_buf(
478 1x cd.certificate_chain.data(),
479 1x static_cast<int>(cd.certificate_chain.size()));
480
1/2
✓ Branch 127 → 128 taken 1 time.
✗ Branch 127 → 138 not taken.
1x if (bio)
481 {
482 X509* entity =
483
1/1
✓ Branch 128 → 129 taken 1 time.
1x PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
484
1/2
✓ Branch 129 → 130 taken 1 time.
✗ Branch 129 → 132 not taken.
1x if (entity)
485 {
486
1/1
✓ Branch 130 → 131 taken 1 time.
1x SSL_CTX_use_certificate(ctx_, entity);
487
1/1
✓ Branch 131 → 132 taken 1 time.
1x X509_free(entity);
488 }
489
490 X509* cert;
491
1/1
✓ Branch 134 → 135 taken 2 times.
2x while ((cert = PEM_read_bio_X509(
492
2/2
✓ Branch 135 → 133 taken 1 time.
✓ Branch 135 → 136 taken 1 time.
2x bio, nullptr, nullptr, nullptr)) != nullptr)
493 {
494
1/1
✓ Branch 133 → 134 taken 1 time.
1x SSL_CTX_add_extra_chain_cert(ctx_, cert);
495 }
496
1/1
✓ Branch 136 → 137 taken 1 time.
1x ERR_clear_error();
497
1/1
✓ Branch 137 → 138 taken 1 time.
1x BIO_free(bio);
498 }
499 }
500
501
6/6
✓ Branch 139 → 140 taken 2883 times.
✓ Branch 139 → 143 taken 5 times.
✓ Branch 141 → 142 taken 785 times.
✓ Branch 141 → 143 taken 2098 times.
✓ Branch 144 → 145 taken 785 times.
✓ Branch 144 → 165 taken 2103 times.
2888x if (cd.pkcs12_data.empty() && !cd.private_key.empty())
502 {
503
1/1
✓ Branch 147 → 148 taken 785 times.
785x BIO* bio = BIO_new_mem_buf(
504 785x cd.private_key.data(), static_cast<int>(cd.private_key.size()));
505
1/2
✓ Branch 148 → 149 taken 785 times.
✗ Branch 148 → 164 not taken.
785x if (bio)
506 {
507 785x EVP_PKEY* pkey = nullptr;
508
2/2
✓ Branch 149 → 150 taken 784 times.
✓ Branch 149 → 156 taken 1 time.
785x if (cd.private_key_format == tls_file_format::pem)
509 {
510
2/2
✓ Branch 151 → 152 taken 2 times.
✓ Branch 151 → 154 taken 782 times.
784x if (cd.password_callback)
511
1/1
✓ Branch 152 → 153 taken 2 times.
2x pkey = PEM_read_bio_PrivateKey(
512 bio, nullptr, password_callback,
513 const_cast<tls_context_data*>(&cd));
514 else
515
1/1
✓ Branch 154 → 155 taken 782 times.
782x pkey = PEM_read_bio_PrivateKey(
516 bio, nullptr, nullptr, nullptr);
517 }
518 else
519
1/1
✓ Branch 156 → 157 taken 1 time.
1x pkey = d2i_PrivateKey_bio(bio, nullptr);
520 // A key that fails to parse or decrypt (wrong or missing
521 // password) must fail setup, not surface later as an
522 // inexplicable handshake error.
523
2/2
✓ Branch 158 → 159 taken 783 times.
✓ Branch 158 → 161 taken 2 times.
785x if (pkey)
524 {
525
1/1
✓ Branch 159 → 160 taken 783 times.
783x SSL_CTX_use_PrivateKey(ctx_, pkey);
526
1/1
✓ Branch 160 → 162 taken 783 times.
783x EVP_PKEY_free(pkey);
527 }
528 else
529 {
530 2x setup_failed_ = true;
531 }
532
1/1
✓ Branch 162 → 163 taken 785 times.
785x BIO_free(bio);
533
1/1
✓ Branch 163 → 165 taken 785 times.
785x ERR_clear_error();
534 }
535 else
536 {
537 setup_failed_ = true;
538 }
539 }
540
541
1/1
✓ Branch 165 → 166 taken 2888 times.
2888x X509_STORE* store = SSL_CTX_get_cert_store(ctx_);
542
2/2
✓ Branch 191 → 168 taken 2111 times.
✓ Branch 191 → 192 taken 2888 times.
4999x for (auto const& ca : cd.ca_certificates)
543 {
544 // A trust anchor that fails to parse or add must not pass
545 // silently: the store would verify against fewer anchors than
546 // requested and reject a legitimate peer as untrusted. Fail
547 // closed instead, tolerating only a duplicate the store already
548 // holds.
549
1/1
✓ Branch 171 → 172 taken 2111 times.
2111x BIO* bio = BIO_new_mem_buf(ca.data(), static_cast<int>(ca.size()));
550
1/2
✗ Branch 172 → 173 not taken.
✓ Branch 172 → 174 taken 2111 times.
2111x if (!bio)
551 {
552 setup_failed_ = true;
553 continue;
554 }
555
1/1
✓ Branch 174 → 175 taken 2111 times.
2111x X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
556
1/2
✓ Branch 175 → 176 taken 2111 times.
✗ Branch 175 → 186 not taken.
2111x if (cert)
557 {
558
3/7
✓ Branch 176 → 177 taken 2111 times.
✗ Branch 177 → 178 not taken.
✓ Branch 177 → 182 taken 2111 times.
✗ Branch 180 → 181 not taken.
✗ Branch 180 → 182 not taken.
✗ Branch 183 → 184 not taken.
✓ Branch 183 → 185 taken 2111 times.
2111x if (X509_STORE_add_cert(store, cert) != 1 &&
559 ERR_GET_REASON(ERR_peek_last_error()) !=
560 X509_R_CERT_ALREADY_IN_HASH_TABLE)
561 setup_failed_ = true;
562
1/1
✓ Branch 185 → 187 taken 2111 times.
2111x X509_free(cert);
563 }
564 else
565 setup_failed_ = true;
566
1/1
✓ Branch 187 → 188 taken 2111 times.
2111x ERR_clear_error();
567
1/1
✓ Branch 188 → 189 taken 2111 times.
2111x BIO_free(bio);
568 }
569
570 // Trust anchors from the system store and explicit directories.
571 // Failures leave the affected source unloaded rather than aborting
572 // context creation; the error queue is cleared so it does not leak
573 // into a later handshake.
574
2/2
✓ Branch 192 → 193 taken 1 time.
✓ Branch 192 → 194 taken 2887 times.
2888x if (cd.use_default_verify_paths)
575
1/1
✓ Branch 193 → 194 taken 1 time.
1x SSL_CTX_set_default_verify_paths(ctx_);
576
2/2
✓ Branch 201 → 196 taken 1 time.
✓ Branch 201 → 202 taken 2888 times.
2889x for (auto const& path : cd.verify_paths)
577
1/1
✓ Branch 198 → 199 taken 1 time.
1x SSL_CTX_load_verify_locations(ctx_, nullptr, path.c_str());
578
1/1
✓ Branch 202 → 203 taken 2888 times.
2888x ERR_clear_error();
579
580 // Certificate revocation via CRLs. Load any supplied CRLs and, when
581 // a revocation policy is active, enable leaf CRL checking. soft_fail
582 // vs hard_fail is applied in the verify trampoline. CRL_CHECK (leaf
583 // only) is used so a missing CRL for a trusted root is not itself an
584 // error.
585
2/2
✓ Branch 203 → 204 taken 3 times.
✓ Branch 203 → 227 taken 2885 times.
2888x if (cd.revocation != tls_revocation_policy::disabled)
586 {
587
2/2
✓ Branch 224 → 206 taken 2 times.
✓ Branch 224 → 225 taken 3 times.
5x for (auto const& crl_data : cd.crls)
588 {
589
1/1
✓ Branch 209 → 210 taken 2 times.
2x BIO* bio = BIO_new_mem_buf(
590 2x crl_data.data(), static_cast<int>(crl_data.size()));
591
1/2
✗ Branch 210 → 211 not taken.
✓ Branch 210 → 212 taken 2 times.
2x if (!bio)
592 {
593 setup_failed_ = true;
594 continue;
595 }
596 // Accept PEM or DER (the documented contract). Try PEM first,
597 // then rewind and try DER.
598 X509_CRL* crl =
599
1/1
✓ Branch 212 → 213 taken 2 times.
2x PEM_read_bio_X509_CRL(bio, nullptr, nullptr, nullptr);
600
2/2
✓ Branch 213 → 214 taken 1 time.
✓ Branch 213 → 217 taken 1 time.
2x if (!crl)
601 {
602
1/1
✓ Branch 214 → 215 taken 1 time.
1x BIO_reset(bio);
603
1/1
✓ Branch 215 → 216 taken 1 time.
1x crl = d2i_X509_CRL_bio(bio, nullptr);
604 }
605
2/2
✓ Branch 217 → 218 taken 1 time.
✓ Branch 217 → 220 taken 1 time.
2x if (crl)
606 {
607
1/1
✓ Branch 218 → 219 taken 1 time.
1x X509_STORE_add_crl(store, crl);
608
1/1
✓ Branch 219 → 221 taken 1 time.
1x X509_CRL_free(crl);
609 }
610 else
611 {
612 // A supplied CRL that parses as neither PEM nor DER must
613 // not be silently dropped; record it so the handshake
614 // fails closed rather than weakening revocation.
615 1x setup_failed_ = true;
616 }
617
1/1
✓ Branch 221 → 222 taken 2 times.
2x BIO_free(bio);
618 }
619
1/1
✓ Branch 225 → 226 taken 3 times.
3x X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK);
620
1/1
✓ Branch 226 → 227 taken 3 times.
3x ERR_clear_error();
621 }
622
623
1/1
✓ Branch 227 → 228 taken 2888 times.
2888x SSL_CTX_set_verify_depth(ctx_, cd.verify_depth);
624
625 // Cipher configuration. TLS 1.2-and-below use the cipher list;
626 // TLS 1.3 uses the separate ciphersuites API. The security level
627 // is deliberately left at the library default: a weak cipher
628 // string should fail loudly rather than be silently permitted via
629 // a forced @SECLEVEL=0. Callers that genuinely need a lower level
630 // can express it in the cipher string (e.g. "...:@SECLEVEL=0").
631 // A cipher string the library rejects must not silently fall back to
632 // the default suites; fail closed instead.
633
3/4
✓ Branch 229 → 230 taken 2 times.
✓ Branch 229 → 234 taken 2886 times.
✗ Branch 235 → 236 not taken.
✓ Branch 235 → 237 taken 2888 times.
2890x if (!cd.ciphersuites.empty() &&
634
2/3
✓ Branch 231 → 232 taken 2 times.
✗ Branch 232 → 233 not taken.
✓ Branch 232 → 234 taken 2 times.
2x !SSL_CTX_set_cipher_list(ctx_, cd.ciphersuites.c_str()))
635 setup_failed_ = true;
636
4/4
✓ Branch 238 → 239 taken 5 times.
✓ Branch 238 → 243 taken 2883 times.
✓ Branch 244 → 245 taken 1 time.
✓ Branch 244 → 246 taken 2887 times.
2893x if (!cd.ciphersuites_tls13.empty() &&
637
3/3
✓ Branch 240 → 241 taken 5 times.
✓ Branch 241 → 242 taken 1 time.
✓ Branch 241 → 243 taken 4 times.
5x !SSL_CTX_set_ciphersuites(ctx_, cd.ciphersuites_tls13.c_str()))
638 1x setup_failed_ = true;
639 }
640
641 5776x ~openssl_native_context() override
642 2888x {
643
1/2
✓ Branch 2 → 3 taken 2888 times.
✗ Branch 2 → 4 not taken.
2888x if (ctx_)
644 2888x SSL_CTX_free(ctx_);
645 5776x }
646 };
647
648 inline openssl_native_context*
649 2893x get_openssl_native_context(tls_context_data const& cd)
650 {
651 static char key;
652
2/4
✓ Branch 2 → 3 taken 2893 times.
✓ Branch 3 → 4 taken 2888 times.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
5781x auto* p = cd.find(&key, [&] { return new openssl_native_context(cd); });
653 2893x return static_cast<openssl_native_context*>(p);
654 }
655
656 //
657 // engine
658 //
659
660 namespace openssl {
661
662 4215x engine::~engine()
663 {
664
2/2
✓ Branch 2 → 3 taken 2893 times.
✓ Branch 2 → 4 taken 1322 times.
4215x if (ext_bio_)
665 2893x BIO_free(ext_bio_);
666
2/2
✓ Branch 4 → 5 taken 2893 times.
✓ Branch 4 → 6 taken 1322 times.
4215x if (ssl_)
667 2893x SSL_free(ssl_);
668 4215x }
669
670 std::error_code
671 2893x engine::init(tls_context const& ctx)
672 {
673 2893x auto& cd = get_tls_context_data(ctx);
674
1/1
✓ Branch 3 → 4 taken 2893 times.
2893x nc_ = get_openssl_native_context(cd);
675
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 2893 times.
2893x if (!nc_->ctx_)
676 {
677 // The cache retains a failed context build permanently (it
678 // never retries), so a later construction can reach here
679 // with an already-drained error queue; make_openssl_error(0)
680 // is falsy and would let the caller treat this as success and
681 // dereference a null ssl_ on first use. Report unconditionally
682 // rather than trust ERR_get_error() to be nonzero.
683 return std::make_error_code(std::errc::not_enough_memory);
684 }
685
686
1/1
✓ Branch 6 → 7 taken 2893 times.
2893x ssl_ = SSL_new(nc_->ctx_);
687
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 10 taken 2893 times.
2893x if (!ssl_)
688 {
689 unsigned long err = ERR_get_error();
690 return make_openssl_error(err);
691 }
692
693 2893x BIO* int_bio = nullptr;
694
2/3
✓ Branch 10 → 11 taken 2893 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 15 taken 2893 times.
2893x if (!BIO_new_bio_pair(&int_bio, 0, &ext_bio_, 0))
695 {
696 unsigned long err = ERR_get_error();
697 SSL_free(ssl_);
698 ssl_ = nullptr;
699 return make_openssl_error(err);
700 }
701
702
1/1
✓ Branch 15 → 16 taken 2893 times.
2893x SSL_set_bio(ssl_, int_bio, int_bio);
703
704 2893x return {};
705 }
706
707 void
708 65x engine::reset()
709 {
710
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 65 times.
65x if (!ssl_)
711 return;
712
713 // Preserves SSL* and BIO pair, releases session state
714
2/3
✓ Branch 4 → 5 taken 65 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 65 times.
65x if (SSL_clear(ssl_) != 1)
715 clear_failed_ = true;
716
717 // SSL_clear() retains the negotiated session so a subsequent
718 // handshake on this SSL* can resume it. Resumed handshakes skip
719 // certificate/hostname re-verification, which would let a changed
720 // set_hostname() go unchecked after reset(); drop it to force a
721 // full handshake. A failure leaves the old session resumable,
722 // defeating that guarantee, so latch the same failure flag
723 // SSL_clear() uses above.
724
2/3
✓ Branch 7 → 8 taken 65 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 65 times.
65x if (SSL_set_session(ssl_, nullptr) != 1)
725 clear_failed_ = true;
726
727 // Drain stale data from the external BIO. Mirrors the driver's
728 // flush guard: a read failure here would otherwise spin the loop
729 // forever since the pending count never advances.
730 char drain[1024];
731
2/3
✓ Branch 14 → 15 taken 65 times.
✗ Branch 15 → 11 not taken.
✓ Branch 15 → 16 taken 65 times.
65x while (BIO_ctrl_pending(ext_bio_) > 0)
732 {
733 if (BIO_read(ext_bio_, drain, sizeof(drain)) <= 0)
734 break;
735 }
736 }
737
738 bool
739 5805x engine::context_setup_failed() const noexcept
740 {
741 // Before the deferred init runs there is no native context to
742 // judge; prepare() re-checks once it exists.
743
4/4
✓ Branch 2 → 3 taken 2936 times.
✓ Branch 2 → 5 taken 2869 times.
✓ Branch 3 → 4 taken 8 times.
✓ Branch 3 → 5 taken 2928 times.
5805x return nc_ && nc_->setup_failed_;
744 }
745
746 std::error_code
747 5805x engine::check_context() const noexcept
748 {
749
2/2
✓ Branch 3 → 4 taken 8 times.
✓ Branch 3 → 5 taken 5797 times.
5805x if (context_setup_failed())
750 8x return std::make_error_code(std::errc::invalid_argument);
751 5797x return {};
752 }
753
754 std::error_code
755 2934x engine::check_session() const noexcept
756 {
757
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 2934 times.
2934x if (clear_failed_)
758 return std::make_error_code(std::errc::invalid_argument);
759 2934x return {};
760 }
761
762 std::error_code
763 2934x engine::prepare(tls_context const& ctx, tls_role role, std::string const& hostname)
764 {
765 // Session creation is deferred from construction so a setup
766 // failure reports through the handshake completion.
767
2/2
✓ Branch 2 → 3 taken 2869 times.
✓ Branch 2 → 12 taken 65 times.
2934x if (!ssl_)
768 {
769
2/3
✓ Branch 3 → 4 taken 2869 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 2869 times.
2869x if (auto ec = init(ctx))
770 return ec;
771 // The driver's check_context gate ran before this init could
772 // populate the native context; re-check so a rejected
773 // configuration still fails closed on the first handshake.
774
2/2
✓ Branch 9 → 10 taken 6 times.
✓ Branch 9 → 11 taken 2863 times.
2869x if (auto cec = check_context())
775 6x return cec;
776 }
777
778 // The hostname applies to client handshakes only; a server
779 // handshake clears any name left by a prior client-role
780 // handshake so client certificates are never hostname-matched.
781 2928x std::string const no_name;
782
4/5
✓ Branch 13 → 14 taken 806 times.
✓ Branch 13 → 15 taken 2122 times.
✓ Branch 16 → 17 taken 2928 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 2928 times.
2928x if (!apply_hostname(role == tls_role::client ? hostname : no_name))
783 {
784 // Fail closed rather than handshake without the requested
785 // name check.
786 return std::make_error_code(std::errc::invalid_argument);
787 }
788
789 // Client offers its ALPN protocol list; the server selects via
790 // the context callback. Role is only known here, so install the
791 // offer per-session; fail closed rather than negotiate nothing
792 // silently.
793
5/7
✓ Branch 19 → 20 taken 2122 times.
✓ Branch 19 → 23 taken 806 times.
✓ Branch 20 → 21 taken 2122 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 2122 times.
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 2928 times.
2928x if (role == tls_role::client && !apply_alpn_offer())
794 return std::make_error_code(std::errc::invalid_argument);
795
796 2928x return {};
797 2928x }
798
799 bool
800 2928x engine::apply_hostname(std::string const& hostname)
801 {
802 2928x return apply_hostname_verification(ssl_, hostname);
803 }
804
805 bool
806 2122x engine::apply_alpn_offer()
807 {
808
2/2
✓ Branch 3 → 4 taken 2120 times.
✓ Branch 3 → 5 taken 2 times.
2122x if (nc_->alpn_wire_.empty())
809 2120x return true;
810
811 // SSL_set_alpn_protos uses the inverted convention: 0 = success.
812 // A non-zero return (allocation failure) means the offer was not
813 // installed; the caller fails closed rather than negotiate nothing
814 // silently.
815 2x return SSL_set_alpn_protos(
816 ssl_,
817 2x reinterpret_cast<unsigned char const*>(nc_->alpn_wire_.data()),
818 4x static_cast<unsigned int>(nc_->alpn_wire_.size())) == 0;
819 }
820
821 void
822 1268x engine::capture_alpn(std::string& out) const
823 {
824 1268x unsigned char const* data = nullptr;
825 1268x unsigned int len = 0;
826
1/1
✓ Branch 2 → 3 taken 1268 times.
1268x SSL_get0_alpn_selected(ssl_, &data, &len);
827
3/4
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 6 taken 1266 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 6 not taken.
1268x if (data && len)
828
1/1
✓ Branch 5 → 6 taken 2 times.
2x out.assign(reinterpret_cast<char const*>(data), len);
829 1268x }
830
831 engine_result
832 147456x engine::perform(engine_op op, void* data, std::size_t len)
833 {
834 // No session exists until the first handshake's deferred init;
835 // report I/O attempted before then instead of crashing on a null
836 // SSL handle.
837
2/2
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 5 taken 147453 times.
147456x if (!ssl_)
838 return {engine_want::done,
839 3x std::make_error_code(std::errc::invalid_argument), 0};
840
841
1/1
✓ Branch 5 → 6 taken 147453 times.
147453x ERR_clear_error();
842
843 147453x int ret = 0;
844
5/6
✓ Branch 6 → 7 taken 4873 times.
✓ Branch 6 → 9 taken 2793 times.
✓ Branch 6 → 11 taken 93007 times.
✓ Branch 6 → 13 taken 46442 times.
✓ Branch 6 → 15 taken 338 times.
✗ Branch 6 → 17 not taken.
147453x switch (op)
845 {
846 4873x case engine_op::handshake_client:
847
1/1
✓ Branch 7 → 8 taken 4873 times.
4873x ret = SSL_connect(ssl_);
848 4873x break;
849 2793x case engine_op::handshake_server:
850
1/1
✓ Branch 9 → 10 taken 2793 times.
2793x ret = SSL_accept(ssl_);
851 2793x break;
852 93007x case engine_op::read:
853
1/1
✓ Branch 11 → 12 taken 93007 times.
93007x ret = SSL_read(ssl_, data, static_cast<int>(len));
854 93007x break;
855 46442x case engine_op::write:
856
1/1
✓ Branch 13 → 14 taken 46442 times.
46442x ret = SSL_write(ssl_, data, static_cast<int>(len));
857 46442x break;
858 338x case engine_op::shutdown:
859
1/1
✓ Branch 15 → 16 taken 338 times.
338x ret = SSL_shutdown(ssl_);
860 338x break;
861 }
862
863
4/4
✓ Branch 17 → 18 taken 54446 times.
✓ Branch 17 → 19 taken 93007 times.
✓ Branch 18 → 19 taken 46442 times.
✓ Branch 18 → 20 taken 8004 times.
147453x bool const transfer = op == engine_op::read || op == engine_op::write;
864
865 // SSL_shutdown returning 0 means our close_notify was queued but
866 // the peer's has not arrived yet: flush it, then read for it.
867
4/4
✓ Branch 21 → 22 taken 338 times.
✓ Branch 21 → 29 taken 147115 times.
✓ Branch 22 → 23 taken 97 times.
✓ Branch 22 → 29 taken 241 times.
147453x if (op == engine_op::shutdown && ret == 0)
868 return {
869
2/3
✓ Branch 23 → 24 taken 97 times.
✓ Branch 24 → 25 taken 97 times.
✗ Branch 24 → 26 not taken.
97x pending_output() > 0 ? engine_want::output_then_retry
870 : engine_want::input,
871 97x {}, 0};
872
873
4/4
✓ Branch 29 → 30 taken 139449 times.
✓ Branch 29 → 31 taken 7907 times.
✓ Branch 32 → 33 taken 94216 times.
✓ Branch 32 → 42 taken 53140 times.
147356x if (transfer ? ret > 0 : ret == 1)
874 return {
875
3/3
✓ Branch 33 → 34 taken 94216 times.
✓ Branch 34 → 35 taken 47770 times.
✓ Branch 34 → 36 taken 46446 times.
94216x pending_output() > 0 ? engine_want::output_then_done
876 : engine_want::done,
877
2/2
✓ Branch 38 → 39 taken 92830 times.
✓ Branch 38 → 40 taken 1386 times.
94216x {}, transfer ? static_cast<std::size_t>(ret) : 0};
878
879
1/1
✓ Branch 42 → 43 taken 53140 times.
53140x int const err = SSL_get_error(ssl_, ret);
880
881
2/2
✓ Branch 43 → 44 taken 1 time.
✓ Branch 43 → 46 taken 53139 times.
53140x if (err == SSL_ERROR_WANT_WRITE)
882 1x return {engine_want::output_then_retry, {}, 0};
883
884
2/2
✓ Branch 46 → 47 taken 53048 times.
✓ Branch 46 → 53 taken 91 times.
53139x if (err == SSL_ERROR_WANT_READ)
885 return {
886
3/3
✓ Branch 47 → 48 taken 53048 times.
✓ Branch 48 → 49 taken 2826 times.
✓ Branch 48 → 50 taken 50222 times.
53048x pending_output() > 0 ? engine_want::output_then_retry
887 : engine_want::input,
888 53048x {}, 0};
889
890
4/4
✓ Branch 53 → 54 taken 64 times.
✓ Branch 53 → 57 taken 27 times.
✓ Branch 54 → 55 taken 61 times.
✓ Branch 54 → 57 taken 3 times.
91x if (transfer && err == SSL_ERROR_ZERO_RETURN)
891 {
892 // ZERO_RETURN means the peer's close_notify WAS received (an
893 // announced close), unlike the terminal branch's empty-queue
894 // fallback below, which means an unannounced one; report eof,
895 // not stream_truncated. A received close_notify queues no
896 // output, so a plain done skips the flush.
897 61x return {engine_want::done, make_error_code(capy::error::eof), 0};
898 }
899
900 30x std::error_code ec;
901
2/2
✓ Branch 58 → 59 taken 1 time.
✓ Branch 58 → 69 taken 29 times.
30x if (op == engine_op::shutdown)
902 {
903
1/1
✓ Branch 59 → 60 taken 1 time.
1x unsigned long ssl_err = ERR_get_error();
904
1/4
✗ Branch 60 → 61 not taken.
✓ Branch 60 → 67 taken 1 time.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 67 not taken.
1x if (ssl_err == 0 && err == SSL_ERROR_SYSCALL)
905 {
906 // The socket closed without an OpenSSL-level error, but
907 // that can mean either the peer's close_notify already
908 // arrived (this operation's fill, or a concurrent reader,
909 // consumed it) or the peer vanished mid-shutdown without
910 // ever sending one; only RECEIVED_SHUTDOWN tells them
911 // apart, and the documented contract promises
912 // stream_truncated for the latter, matching the read path
913 // and the driver's `map_fill_error` policy.
914 ec = received_shutdown()
915 ? std::error_code{}
916 : make_error_code(capy::error::stream_truncated);
917 }
918 else
919 {
920 1x ec = make_openssl_error(ssl_err);
921 }
922 }
923 else
924 {
925 // SYSCALL and every other terminal code map the same way;
926 // map_openssl_fatal() covers both.
927 29x ec = map_openssl_fatal();
928 }
929 return {
930
3/3
✓ Branch 70 → 71 taken 30 times.
✓ Branch 71 → 72 taken 21 times.
✓ Branch 71 → 73 taken 9 times.
30x pending_output() > 0 ? engine_want::output_then_done
931 : engine_want::done,
932 30x ec, 0};
933 }
934
935 std::size_t
936 59x engine::put_input(unsigned char const* data, std::size_t len)
937 {
938 59x int put = BIO_write(ext_bio_, data, static_cast<int>(len));
939 59x return put > 0 ? static_cast<std::size_t>(put) : 0;
940 }
941
942 std::pair<unsigned char*, std::size_t>
943 50215x engine::input_area()
944 {
945 // A BIO pair hands out a pointer into its own buffer, so the
946 // transport reads ciphertext directly into the pair with no staging
947 // copy. The buffer is circular: this is only the contiguous run to
948 // the wrap, which the driver's read loop already tolerates.
949 50215x char* p = nullptr;
950
1/1
✓ Branch 2 → 3 taken 50215 times.
50215x int const cap = BIO_nwrite0(ext_bio_, &p);
951
2/4
✓ Branch 3 → 4 taken 50215 times.
✗ Branch 3 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 50215 times.
50215x if (cap <= 0 || !p)
952 return {nullptr, 0};
953 50215x return {reinterpret_cast<unsigned char*>(p), static_cast<std::size_t>(cap)};
954 }
955
956 void
957 48639x engine::input_committed(std::size_t n)
958 {
959
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 48639 times.
48639x if (n == 0)
960 return;
961 // The bytes were written straight into the region BIO_nwrite0
962 // returned; advance the pair's write cursor to make them readable.
963 48639x char* p = nullptr;
964
1/1
✓ Branch 4 → 5 taken 48639 times.
48639x BIO_nwrite(ext_bio_, &p, static_cast<int>(n));
965 }
966
967 std::size_t
968 349603x engine::pending_output() const
969 {
970 349603x return BIO_ctrl_pending(ext_bio_);
971 }
972
973 std::size_t
974 50713x engine::get_output(unsigned char* data, std::size_t len)
975 {
976 50713x int r = BIO_read(ext_bio_, data, static_cast<int>(len));
977 50713x return r > 0 ? static_cast<std::size_t>(r) : 0;
978 }
979
980 bool
981 1682x engine::received_shutdown() const
982 {
983 1682x return (SSL_get_shutdown(ssl_) & SSL_RECEIVED_SHUTDOWN) != 0;
984 }
985
986 } // namespace openssl
987
988 } // namespace detail
989
990 } // namespace boost::corosio
991