src/openssl/src/detail/engine.cpp

86.7% Lines (345/398) 100.0% List of functions (34/34) 77.8% Branches (332/427)
engine.cpp
f(x) Functions (34)
Function Calls Lines Branches Blocks
boost::corosio::(anonymous namespace)::tls_method_compat() :33 2690x 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 2042x 78.6% 81.2% 88.9% boost::corosio::(anonymous namespace)::openssl_proto_version(boost::corosio::tls_version) :73 5380x 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 32x 75.0% 50.0% 71.4% boost::corosio::(anonymous namespace)::map_openssl_fatal() :132 31x 80.0% 50.0% 80.0% boost::corosio::openssl_category() :143 39x 100.0% 75.0% 100.0% boost::corosio::detail::password_callback(char*, int, int, void*) :158 1x 84.6% 55.6% 81.2% 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 2690x 88.2% 85.3% 87.3% boost::corosio::detail::openssl_native_context::~openssl_native_context() :618 5380x 100.0% 100.0% boost::corosio::detail::get_openssl_native_context(boost::corosio::detail::tls_context_data const&) :626 2696x 100.0% 100.0% boost::corosio::detail::get_openssl_native_context(boost::corosio::detail::tls_context_data const&)::{lambda()#1}::operator()() const :629 2690x 100.0% 50.0% 71.4% boost::corosio::detail::openssl::engine::~engine() :639 2696x 100.0% 50.0% 100.0% boost::corosio::detail::openssl::engine::init(boost::corosio::tls_context const&) :648 2696x 58.8% 53.8% 66.7% boost::corosio::detail::openssl::engine::reset() :685 65x 50.0% 50.0% 64.7% boost::corosio::detail::openssl::engine::context_setup_failed() const :716 2047x 100.0% 100.0% boost::corosio::detail::openssl::engine::check_context() const :722 2047x 100.0% 100.0% 100.0% boost::corosio::detail::openssl::engine::check_session() const :730 2042x 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&) :738 2042x 75.0% 75.0% 76.2% boost::corosio::detail::openssl::engine::apply_hostname(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) :762 2042x 100.0% 100.0% boost::corosio::detail::openssl::engine::apply_alpn_offer() :768 1365x 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 :784 1008x 100.0% 83.3% 100.0% boost::corosio::detail::openssl::engine::perform(boost::corosio::detail::engine_op, void*, unsigned long long) :794 95846x 93.3% 86.0% 89.2% boost::corosio::detail::openssl::engine::put_input(unsigned char const*, unsigned long long) :891 55x 100.0% 100.0% boost::corosio::detail::openssl::engine::input_area() :898 32718x 83.3% 60.0% 80.0% boost::corosio::detail::openssl::engine::input_committed(unsigned long long) :912 31770x 80.0% 66.7% 83.3% boost::corosio::detail::openssl::engine::pending_output() const :923 227611x 100.0% 100.0% boost::corosio::detail::openssl::engine::get_output(unsigned char*, unsigned long long) :929 33083x 100.0% 100.0% boost::corosio::detail::openssl::engine::received_shutdown() const :936 1054x 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 2690x tls_method_compat() noexcept
34 {
35 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
36 2690x return TLS_method();
37 #else
38 return SSLv23_method();
39 #endif
40 }
41
42 inline bool
43 2042x 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 2029 times.
✓ Branch 3 → 5 taken 13 times.
2042x 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 2029 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 11 times.
2042x bool const is_ip = name && detail::is_ip_literal(hostname);
55
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 13 taken 2040 times.
2042x char const* dns_name = is_ip ? nullptr : name;
56
57
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 2042 times.
2042x if (SSL_set_tlsext_host_name(ssl, dns_name) != 1)
58 return false;
59
60 2042x auto* param = SSL_get0_param(ssl);
61
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 2042 times.
2042x if (!param)
62 return name == nullptr;
63
64
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 2042 times.
2042x 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 2040 times.
2042x if (is_ip)
67 2x return X509_VERIFY_PARAM_set1_ip_asc(param, name) == 1;
68 2040x 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 5380x openssl_proto_version(tls_version v) noexcept
74 {
75
2/2
✓ Branch 2 → 3 taken 2695 times.
✓ Branch 2 → 4 taken 2685 times.
5380x 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 32x make_openssl_error(unsigned long err) noexcept
118 {
119
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 32 times.
32x 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 32x 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 31x map_openssl_fatal() noexcept
133 {
134 31x unsigned long ssl_err = ERR_get_error();
135
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 31 times.
31x if (ssl_err == 0)
136 return make_error_code(capy::error::stream_truncated);
137 31x return make_openssl_error(ssl_err);
138 }
139
140 } // namespace
141
142 std::error_category const&
143 39x openssl_category() noexcept
144 {
145
3/4
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 7 taken 37 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 7 not taken.
39x static openssl_category_impl instance;
146 39x 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 1x password_callback(char* buf, int size, int rwflag, void* userdata)
159 {
160 1x auto* cd = static_cast<tls_context_data const*>(userdata);
161
3/6
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
1x if (!cd || !cd->password_callback)
162 return 0;
163
164 1x tls_password_purpose purpose = (rwflag == 0)
165 1x ? tls_password_purpose::for_reading
166 : tls_password_purpose::for_writing;
167
168 std::string password =
169
1/1
✓ Branch 9 → 10 taken 1 time.
1x cd->password_callback(static_cast<std::size_t>(size), purpose);
170
171 1x int len = static_cast<int>(password.size());
172
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 1 time.
1x if (len > size)
173 len = size;
174
175 1x std::memcpy(buf, password.data(), static_cast<std::size_t>(len));
176 1x return len;
177 1x }
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 2690x explicit openssl_native_context(tls_context_data const& cd)
320 5380x : ctx_(nullptr)
321 2690x , cd_(&cd)
322 {
323
1/1
✓ Branch 6 → 7 taken 2690 times.
2690x ctx_ = SSL_CTX_new(tls_method_compat());
324
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 2690 times.
2690x if (!ctx_)
325 return;
326
327
2/2
✓ Branch 9 → 10 taken 5 times.
✓ Branch 9 → 12 taken 2685 times.
2690x 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 2690 times.
2690x 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 2682 times.
2690x 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 2686 times.
2690x 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 2690 times.
2690x SSL_CTX_set_mode(ctx_, SSL_MODE_ENABLE_PARTIAL_WRITE);
349
1/1
✓ Branch 25 → 26 taken 2690 times.
2690x 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 2690 times.
2690x 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 2689 times.
2690x if (cd.min_version > cd.max_version)
358 1x setup_failed_ = true;
359
2/3
✓ Branch 30 → 31 taken 2690 times.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 2690 times.
2690x 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 2690 times.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 37 taken 2690 times.
2690x if (!SSL_CTX_set_max_proto_version(
363 ctx_, openssl_proto_version(cd.max_version)))
364 setup_failed_ = true;
365
366 2690x int verify_mode_flag = SSL_VERIFY_NONE;
367
2/2
✓ Branch 37 → 38 taken 1342 times.
✓ Branch 37 → 39 taken 1348 times.
2690x if (cd.verification_mode == tls_verify_mode::peer)
368 1342x verify_mode_flag = SSL_VERIFY_PEER;
369
2/2
✓ Branch 39 → 40 taken 3 times.
✓ Branch 39 → 41 taken 1345 times.
1348x 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 2685 times.
✓ Branch 42 → 44 taken 5 times.
5375x cd.verify_callback ||
376
2/2
✓ Branch 43 → 44 taken 3 times.
✓ Branch 43 → 45 taken 2682 times.
5375x cd.revocation != tls_revocation_policy::disabled;
377
3/3
✓ Branch 46 → 47 taken 8 times.
✓ Branch 46 → 48 taken 2682 times.
✓ Branch 49 → 50 taken 2690 times.
2690x 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 2685 times.
2690x 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 2685 times.
✓ Branch 96 → 100 taken 5 times.
✓ Branch 98 → 99 taken 1341 times.
✓ Branch 98 → 100 taken 1344 times.
✓ Branch 101 → 102 taken 1341 times.
✓ Branch 101 → 115 taken 1349 times.
2690x if (cd.pkcs12_data.empty() && !cd.entity_certificate.empty())
444 {
445
1/1
✓ Branch 104 → 105 taken 1341 times.
1341x BIO* bio = BIO_new_mem_buf(
446 1341x cd.entity_certificate.data(),
447 1341x static_cast<int>(cd.entity_certificate.size()));
448
1/2
✓ Branch 105 → 106 taken 1341 times.
✗ Branch 105 → 115 not taken.
1341x if (bio)
449 {
450 1341x X509* cert = nullptr;
451
1/2
✓ Branch 106 → 107 taken 1341 times.
✗ Branch 106 → 109 not taken.
1341x if (cd.entity_cert_format == tls_file_format::pem)
452
1/1
✓ Branch 107 → 108 taken 1341 times.
1341x cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
453 else
454 cert = d2i_X509_bio(bio, nullptr);
455
2/2
✓ Branch 111 → 112 taken 1340 times.
✓ Branch 111 → 114 taken 1 time.
1341x if (cert)
456 {
457
1/1
✓ Branch 112 → 113 taken 1340 times.
1340x SSL_CTX_use_certificate(ctx_, cert);
458
1/1
✓ Branch 113 → 114 taken 1340 times.
1340x X509_free(cert);
459 }
460
1/1
✓ Branch 114 → 115 taken 1341 times.
1341x BIO_free(bio);
461 }
462 }
463
464
6/6
✓ Branch 116 → 117 taken 2685 times.
✓ Branch 116 → 120 taken 5 times.
✓ Branch 118 → 119 taken 1 time.
✓ Branch 118 → 120 taken 2684 times.
✓ Branch 121 → 122 taken 1 time.
✓ Branch 121 → 136 taken 2689 times.
2690x if (cd.pkcs12_data.empty() && !cd.certificate_chain.empty())
465 {
466
1/1
✓ Branch 124 → 125 taken 1 time.
1x BIO* bio = BIO_new_mem_buf(
467 1x cd.certificate_chain.data(),
468 1x static_cast<int>(cd.certificate_chain.size()));
469
1/2
✓ Branch 125 → 126 taken 1 time.
✗ Branch 125 → 136 not taken.
1x if (bio)
470 {
471 X509* entity =
472
1/1
✓ Branch 126 → 127 taken 1 time.
1x PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
473
1/2
✓ Branch 127 → 128 taken 1 time.
✗ Branch 127 → 130 not taken.
1x if (entity)
474 {
475
1/1
✓ Branch 128 → 129 taken 1 time.
1x SSL_CTX_use_certificate(ctx_, entity);
476
1/1
✓ Branch 129 → 130 taken 1 time.
1x X509_free(entity);
477 }
478
479 X509* cert;
480
1/1
✓ Branch 132 → 133 taken 2 times.
2x while ((cert = PEM_read_bio_X509(
481
2/2
✓ Branch 133 → 131 taken 1 time.
✓ Branch 133 → 134 taken 1 time.
2x bio, nullptr, nullptr, nullptr)) != nullptr)
482 {
483
1/1
✓ Branch 131 → 132 taken 1 time.
1x SSL_CTX_add_extra_chain_cert(ctx_, cert);
484 }
485
1/1
✓ Branch 134 → 135 taken 1 time.
1x ERR_clear_error();
486
1/1
✓ Branch 135 → 136 taken 1 time.
1x BIO_free(bio);
487 }
488 }
489
490
6/6
✓ Branch 137 → 138 taken 2685 times.
✓ Branch 137 → 141 taken 5 times.
✓ Branch 139 → 140 taken 1342 times.
✓ Branch 139 → 141 taken 1343 times.
✓ Branch 142 → 143 taken 1342 times.
✓ Branch 142 → 160 taken 1348 times.
2690x if (cd.pkcs12_data.empty() && !cd.private_key.empty())
491 {
492
1/1
✓ Branch 145 → 146 taken 1342 times.
1342x BIO* bio = BIO_new_mem_buf(
493 1342x cd.private_key.data(), static_cast<int>(cd.private_key.size()));
494
1/2
✓ Branch 146 → 147 taken 1342 times.
✗ Branch 146 → 160 not taken.
1342x if (bio)
495 {
496 1342x EVP_PKEY* pkey = nullptr;
497
1/2
✓ Branch 147 → 148 taken 1342 times.
✗ Branch 147 → 154 not taken.
1342x if (cd.private_key_format == tls_file_format::pem)
498 {
499
2/2
✓ Branch 149 → 150 taken 1 time.
✓ Branch 149 → 152 taken 1341 times.
1342x if (cd.password_callback)
500
1/1
✓ Branch 150 → 151 taken 1 time.
1x pkey = PEM_read_bio_PrivateKey(
501 bio, nullptr, password_callback,
502 const_cast<tls_context_data*>(&cd));
503 else
504
1/1
✓ Branch 152 → 153 taken 1341 times.
1341x pkey = PEM_read_bio_PrivateKey(
505 bio, nullptr, nullptr, nullptr);
506 }
507 else
508 pkey = d2i_PrivateKey_bio(bio, nullptr);
509
2/2
✓ Branch 156 → 157 taken 1341 times.
✓ Branch 156 → 159 taken 1 time.
1342x if (pkey)
510 {
511
1/1
✓ Branch 157 → 158 taken 1341 times.
1341x SSL_CTX_use_PrivateKey(ctx_, pkey);
512
1/1
✓ Branch 158 → 159 taken 1341 times.
1341x EVP_PKEY_free(pkey);
513 }
514
1/1
✓ Branch 159 → 160 taken 1342 times.
1342x BIO_free(bio);
515 }
516 }
517
518
1/1
✓ Branch 160 → 161 taken 2690 times.
2690x X509_STORE* store = SSL_CTX_get_cert_store(ctx_);
519
2/2
✓ Branch 186 → 163 taken 1354 times.
✓ Branch 186 → 187 taken 2690 times.
4044x for (auto const& ca : cd.ca_certificates)
520 {
521 // A trust anchor that fails to parse or add must not pass
522 // silently: the store would verify against fewer anchors than
523 // requested and reject a legitimate peer as untrusted. Fail
524 // closed instead, tolerating only a duplicate the store already
525 // holds.
526
1/1
✓ Branch 166 → 167 taken 1354 times.
1354x BIO* bio = BIO_new_mem_buf(ca.data(), static_cast<int>(ca.size()));
527
1/2
✗ Branch 167 → 168 not taken.
✓ Branch 167 → 169 taken 1354 times.
1354x if (!bio)
528 {
529 setup_failed_ = true;
530 continue;
531 }
532
1/1
✓ Branch 169 → 170 taken 1354 times.
1354x X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
533
1/2
✓ Branch 170 → 171 taken 1354 times.
✗ Branch 170 → 181 not taken.
1354x if (cert)
534 {
535
3/7
✓ Branch 171 → 172 taken 1354 times.
✗ Branch 172 → 173 not taken.
✓ Branch 172 → 177 taken 1354 times.
✗ Branch 175 → 176 not taken.
✗ Branch 175 → 177 not taken.
✗ Branch 178 → 179 not taken.
✓ Branch 178 → 180 taken 1354 times.
1354x if (X509_STORE_add_cert(store, cert) != 1 &&
536 ERR_GET_REASON(ERR_peek_last_error()) !=
537 X509_R_CERT_ALREADY_IN_HASH_TABLE)
538 setup_failed_ = true;
539
1/1
✓ Branch 180 → 182 taken 1354 times.
1354x X509_free(cert);
540 }
541 else
542 setup_failed_ = true;
543
1/1
✓ Branch 182 → 183 taken 1354 times.
1354x ERR_clear_error();
544
1/1
✓ Branch 183 → 184 taken 1354 times.
1354x BIO_free(bio);
545 }
546
547 // Trust anchors from the system store and explicit directories.
548 // Failures leave the affected source unloaded rather than aborting
549 // context creation; the error queue is cleared so it does not leak
550 // into a later handshake.
551
2/2
✓ Branch 187 → 188 taken 1 time.
✓ Branch 187 → 189 taken 2689 times.
2690x if (cd.use_default_verify_paths)
552
1/1
✓ Branch 188 → 189 taken 1 time.
1x SSL_CTX_set_default_verify_paths(ctx_);
553
2/2
✓ Branch 196 → 191 taken 1 time.
✓ Branch 196 → 197 taken 2690 times.
2691x for (auto const& path : cd.verify_paths)
554
1/1
✓ Branch 193 → 194 taken 1 time.
1x SSL_CTX_load_verify_locations(ctx_, nullptr, path.c_str());
555
1/1
✓ Branch 197 → 198 taken 2690 times.
2690x ERR_clear_error();
556
557 // Certificate revocation via CRLs. Load any supplied CRLs and, when
558 // a revocation policy is active, enable leaf CRL checking. soft_fail
559 // vs hard_fail is applied in the verify trampoline. CRL_CHECK (leaf
560 // only) is used so a missing CRL for a trusted root is not itself an
561 // error.
562
2/2
✓ Branch 198 → 199 taken 3 times.
✓ Branch 198 → 222 taken 2687 times.
2690x if (cd.revocation != tls_revocation_policy::disabled)
563 {
564
2/2
✓ Branch 219 → 201 taken 2 times.
✓ Branch 219 → 220 taken 3 times.
5x for (auto const& crl_data : cd.crls)
565 {
566
1/1
✓ Branch 204 → 205 taken 2 times.
2x BIO* bio = BIO_new_mem_buf(
567 2x crl_data.data(), static_cast<int>(crl_data.size()));
568
1/2
✗ Branch 205 → 206 not taken.
✓ Branch 205 → 207 taken 2 times.
2x if (!bio)
569 {
570 setup_failed_ = true;
571 continue;
572 }
573 // Accept PEM or DER (the documented contract). Try PEM first,
574 // then rewind and try DER.
575 X509_CRL* crl =
576
1/1
✓ Branch 207 → 208 taken 2 times.
2x PEM_read_bio_X509_CRL(bio, nullptr, nullptr, nullptr);
577
2/2
✓ Branch 208 → 209 taken 1 time.
✓ Branch 208 → 212 taken 1 time.
2x if (!crl)
578 {
579
1/1
✓ Branch 209 → 210 taken 1 time.
1x BIO_reset(bio);
580
1/1
✓ Branch 210 → 211 taken 1 time.
1x crl = d2i_X509_CRL_bio(bio, nullptr);
581 }
582
2/2
✓ Branch 212 → 213 taken 1 time.
✓ Branch 212 → 215 taken 1 time.
2x if (crl)
583 {
584
1/1
✓ Branch 213 → 214 taken 1 time.
1x X509_STORE_add_crl(store, crl);
585
1/1
✓ Branch 214 → 216 taken 1 time.
1x X509_CRL_free(crl);
586 }
587 else
588 {
589 // A supplied CRL that parses as neither PEM nor DER must
590 // not be silently dropped; record it so the handshake
591 // fails closed rather than weakening revocation.
592 1x setup_failed_ = true;
593 }
594
1/1
✓ Branch 216 → 217 taken 2 times.
2x BIO_free(bio);
595 }
596
1/1
✓ Branch 220 → 221 taken 3 times.
3x X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK);
597
1/1
✓ Branch 221 → 222 taken 3 times.
3x ERR_clear_error();
598 }
599
600
1/1
✓ Branch 222 → 223 taken 2690 times.
2690x SSL_CTX_set_verify_depth(ctx_, cd.verify_depth);
601
602 // Cipher configuration. TLS 1.2-and-below use the cipher list;
603 // TLS 1.3 uses the separate ciphersuites API. The security level
604 // is deliberately left at the library default: a weak cipher
605 // string should fail loudly rather than be silently permitted via
606 // a forced @SECLEVEL=0. Callers that genuinely need a lower level
607 // can express it in the cipher string (e.g. "...:@SECLEVEL=0").
608 // A cipher string the library rejects must not silently fall back to
609 // the default suites; fail closed instead.
610
3/4
✓ Branch 224 → 225 taken 4 times.
✓ Branch 224 → 229 taken 2686 times.
✗ Branch 230 → 231 not taken.
✓ Branch 230 → 232 taken 2690 times.
2694x if (!cd.ciphersuites.empty() &&
611
2/3
✓ Branch 226 → 227 taken 4 times.
✗ Branch 227 → 228 not taken.
✓ Branch 227 → 229 taken 4 times.
4x !SSL_CTX_set_cipher_list(ctx_, cd.ciphersuites.c_str()))
612 setup_failed_ = true;
613
4/4
✓ Branch 233 → 234 taken 5 times.
✓ Branch 233 → 238 taken 2685 times.
✓ Branch 239 → 240 taken 1 time.
✓ Branch 239 → 241 taken 2689 times.
2695x if (!cd.ciphersuites_tls13.empty() &&
614
3/3
✓ Branch 235 → 236 taken 5 times.
✓ Branch 236 → 237 taken 1 time.
✓ Branch 236 → 238 taken 4 times.
5x !SSL_CTX_set_ciphersuites(ctx_, cd.ciphersuites_tls13.c_str()))
615 1x setup_failed_ = true;
616 }
617
618 5380x ~openssl_native_context() override
619 2690x {
620
1/2
✓ Branch 2 → 3 taken 2690 times.
✗ Branch 2 → 4 not taken.
2690x if (ctx_)
621 2690x SSL_CTX_free(ctx_);
622 5380x }
623 };
624
625 inline openssl_native_context*
626 2696x get_openssl_native_context(tls_context_data const& cd)
627 {
628 static char key;
629
2/4
✓ Branch 2 → 3 taken 2696 times.
✓ Branch 3 → 4 taken 2690 times.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
5386x auto* p = cd.find(&key, [&] { return new openssl_native_context(cd); });
630 2696x return static_cast<openssl_native_context*>(p);
631 }
632
633 //
634 // engine
635 //
636
637 namespace openssl {
638
639 2696x engine::~engine()
640 {
641
1/2
✓ Branch 2 → 3 taken 2696 times.
✗ Branch 2 → 4 not taken.
2696x if (ext_bio_)
642 2696x BIO_free(ext_bio_);
643
1/2
✓ Branch 4 → 5 taken 2696 times.
✗ Branch 4 → 6 not taken.
2696x if (ssl_)
644 2696x SSL_free(ssl_);
645 2696x }
646
647 std::error_code
648 2696x engine::init(tls_context const& ctx)
649 {
650 2696x auto& cd = get_tls_context_data(ctx);
651
1/1
✓ Branch 3 → 4 taken 2696 times.
2696x nc_ = get_openssl_native_context(cd);
652
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 2696 times.
2696x if (!nc_->ctx_)
653 {
654 // The cache retains a failed context build permanently (it
655 // never retries), so a later construction can reach here
656 // with an already-drained error queue; make_openssl_error(0)
657 // is falsy and would let the caller treat this as success and
658 // dereference a null ssl_ on first use. Report unconditionally
659 // rather than trust ERR_get_error() to be nonzero.
660 return std::make_error_code(std::errc::not_enough_memory);
661 }
662
663
1/1
✓ Branch 6 → 7 taken 2696 times.
2696x ssl_ = SSL_new(nc_->ctx_);
664
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 10 taken 2696 times.
2696x if (!ssl_)
665 {
666 unsigned long err = ERR_get_error();
667 return make_openssl_error(err);
668 }
669
670 2696x BIO* int_bio = nullptr;
671
2/3
✓ Branch 10 → 11 taken 2696 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 15 taken 2696 times.
2696x if (!BIO_new_bio_pair(&int_bio, 0, &ext_bio_, 0))
672 {
673 unsigned long err = ERR_get_error();
674 SSL_free(ssl_);
675 ssl_ = nullptr;
676 return make_openssl_error(err);
677 }
678
679
1/1
✓ Branch 15 → 16 taken 2696 times.
2696x SSL_set_bio(ssl_, int_bio, int_bio);
680
681 2696x return {};
682 }
683
684 void
685 65x engine::reset()
686 {
687
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 65 times.
65x if (!ssl_)
688 return;
689
690 // Preserves SSL* and BIO pair, releases session state
691
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)
692 clear_failed_ = true;
693
694 // SSL_clear() retains the negotiated session so a subsequent
695 // handshake on this SSL* can resume it. Resumed handshakes skip
696 // certificate/hostname re-verification, which would let a changed
697 // set_hostname() go unchecked after reset(); drop it to force a
698 // full handshake. A failure leaves the old session resumable,
699 // defeating that guarantee, so latch the same failure flag
700 // SSL_clear() uses above.
701
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)
702 clear_failed_ = true;
703
704 // Drain stale data from the external BIO. Mirrors the driver's
705 // flush guard: a read failure here would otherwise spin the loop
706 // forever since the pending count never advances.
707 char drain[1024];
708
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)
709 {
710 if (BIO_read(ext_bio_, drain, sizeof(drain)) <= 0)
711 break;
712 }
713 }
714
715 bool
716 2047x engine::context_setup_failed() const noexcept
717 {
718 2047x return nc_->setup_failed_;
719 }
720
721 std::error_code
722 2047x engine::check_context() const noexcept
723 {
724
2/2
✓ Branch 3 → 4 taken 5 times.
✓ Branch 3 → 5 taken 2042 times.
2047x if (context_setup_failed())
725 5x return std::make_error_code(std::errc::invalid_argument);
726 2042x return {};
727 }
728
729 std::error_code
730 2042x engine::check_session() const noexcept
731 {
732
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 2042 times.
2042x if (clear_failed_)
733 return std::make_error_code(std::errc::invalid_argument);
734 2042x return {};
735 }
736
737 std::error_code
738 2042x engine::prepare(tls_context const&, tls_role role, std::string const& hostname)
739 {
740 // The hostname applies to client handshakes only; a server
741 // handshake clears any name left by a prior client-role
742 // handshake so client certificates are never hostname-matched.
743 2042x std::string const no_name;
744
4/5
✓ Branch 3 → 4 taken 677 times.
✓ Branch 3 → 5 taken 1365 times.
✓ Branch 6 → 7 taken 2042 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 2042 times.
2042x if (!apply_hostname(role == tls_role::client ? hostname : no_name))
745 {
746 // Fail closed rather than handshake without the requested
747 // name check.
748 return std::make_error_code(std::errc::invalid_argument);
749 }
750
751 // Client offers its ALPN protocol list; the server selects via
752 // the context callback. Role is only known here, so install the
753 // offer per-session; fail closed rather than negotiate nothing
754 // silently.
755
5/7
✓ Branch 9 → 10 taken 1365 times.
✓ Branch 9 → 13 taken 677 times.
✓ Branch 10 → 11 taken 1365 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 1365 times.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 2042 times.
2042x if (role == tls_role::client && !apply_alpn_offer())
756 return std::make_error_code(std::errc::invalid_argument);
757
758 2042x return {};
759 2042x }
760
761 bool
762 2042x engine::apply_hostname(std::string const& hostname)
763 {
764 2042x return apply_hostname_verification(ssl_, hostname);
765 }
766
767 bool
768 1365x engine::apply_alpn_offer()
769 {
770
2/2
✓ Branch 3 → 4 taken 1363 times.
✓ Branch 3 → 5 taken 2 times.
1365x if (nc_->alpn_wire_.empty())
771 1363x return true;
772
773 // SSL_set_alpn_protos uses the inverted convention: 0 = success.
774 // A non-zero return (allocation failure) means the offer was not
775 // installed; the caller fails closed rather than negotiate nothing
776 // silently.
777 2x return SSL_set_alpn_protos(
778 ssl_,
779 2x reinterpret_cast<unsigned char const*>(nc_->alpn_wire_.data()),
780 4x static_cast<unsigned int>(nc_->alpn_wire_.size())) == 0;
781 }
782
783 void
784 1008x engine::capture_alpn(std::string& out) const
785 {
786 1008x unsigned char const* data = nullptr;
787 1008x unsigned int len = 0;
788
1/1
✓ Branch 2 → 3 taken 1008 times.
1008x SSL_get0_alpn_selected(ssl_, &data, &len);
789
3/4
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 6 taken 1006 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 6 not taken.
1008x if (data && len)
790
1/1
✓ Branch 5 → 6 taken 2 times.
2x out.assign(reinterpret_cast<char const*>(data), len);
791 1008x }
792
793 engine_result
794 95846x engine::perform(engine_op op, void* data, std::size_t len)
795 {
796
1/1
✓ Branch 2 → 3 taken 95846 times.
95846x ERR_clear_error();
797
798 95846x int ret = 0;
799
5/6
✓ Branch 3 → 4 taken 3228 times.
✓ Branch 3 → 6 taken 2273 times.
✓ Branch 3 → 8 taken 60045 times.
✓ Branch 3 → 10 taken 29962 times.
✓ Branch 3 → 12 taken 338 times.
✗ Branch 3 → 14 not taken.
95846x switch (op)
800 {
801 3228x case engine_op::handshake_client:
802
1/1
✓ Branch 4 → 5 taken 3228 times.
3228x ret = SSL_connect(ssl_);
803 3228x break;
804 2273x case engine_op::handshake_server:
805
1/1
✓ Branch 6 → 7 taken 2273 times.
2273x ret = SSL_accept(ssl_);
806 2273x break;
807 60045x case engine_op::read:
808
1/1
✓ Branch 8 → 9 taken 60045 times.
60045x ret = SSL_read(ssl_, data, static_cast<int>(len));
809 60045x break;
810 29962x case engine_op::write:
811
1/1
✓ Branch 10 → 11 taken 29962 times.
29962x ret = SSL_write(ssl_, data, static_cast<int>(len));
812 29962x break;
813 338x case engine_op::shutdown:
814
1/1
✓ Branch 12 → 13 taken 338 times.
338x ret = SSL_shutdown(ssl_);
815 338x break;
816 }
817
818
4/4
✓ Branch 14 → 15 taken 35801 times.
✓ Branch 14 → 16 taken 60045 times.
✓ Branch 15 → 16 taken 29962 times.
✓ Branch 15 → 17 taken 5839 times.
95846x bool const transfer = op == engine_op::read || op == engine_op::write;
819
820 // SSL_shutdown returning 0 means our close_notify was queued but
821 // the peer's has not arrived yet: flush it, then read for it.
822
4/4
✓ Branch 18 → 19 taken 338 times.
✓ Branch 18 → 26 taken 95508 times.
✓ Branch 19 → 20 taken 97 times.
✓ Branch 19 → 26 taken 241 times.
95846x if (op == engine_op::shutdown && ret == 0)
823 return {
824
2/3
✓ Branch 20 → 21 taken 97 times.
✓ Branch 21 → 22 taken 97 times.
✗ Branch 21 → 23 not taken.
97x pending_output() > 0 ? engine_want::output_then_retry
825 : engine_want::input,
826 97x {}, 0};
827
828
4/4
✓ Branch 26 → 27 taken 90007 times.
✓ Branch 26 → 28 taken 5742 times.
✓ Branch 29 → 30 taken 60993 times.
✓ Branch 29 → 39 taken 34756 times.
95749x if (transfer ? ret > 0 : ret == 1)
829 return {
830
3/3
✓ Branch 30 → 31 taken 60993 times.
✓ Branch 31 → 32 taken 31028 times.
✓ Branch 31 → 33 taken 29965 times.
60993x pending_output() > 0 ? engine_want::output_then_done
831 : engine_want::done,
832
2/2
✓ Branch 35 → 36 taken 59869 times.
✓ Branch 35 → 37 taken 1124 times.
60993x {}, transfer ? static_cast<std::size_t>(ret) : 0};
833
834
1/1
✓ Branch 39 → 40 taken 34756 times.
34756x int const err = SSL_get_error(ssl_, ret);
835
836
2/2
✓ Branch 40 → 41 taken 1 time.
✓ Branch 40 → 43 taken 34755 times.
34756x if (err == SSL_ERROR_WANT_WRITE)
837 1x return {engine_want::output_then_retry, {}, 0};
838
839
2/2
✓ Branch 43 → 44 taken 34662 times.
✓ Branch 43 → 50 taken 93 times.
34755x if (err == SSL_ERROR_WANT_READ)
840 return {
841
3/3
✓ Branch 44 → 45 taken 34662 times.
✓ Branch 45 → 46 taken 1937 times.
✓ Branch 45 → 47 taken 32725 times.
34662x pending_output() > 0 ? engine_want::output_then_retry
842 : engine_want::input,
843 34662x {}, 0};
844
845
4/4
✓ Branch 50 → 51 taken 64 times.
✓ Branch 50 → 54 taken 29 times.
✓ Branch 51 → 52 taken 61 times.
✓ Branch 51 → 54 taken 3 times.
93x if (transfer && err == SSL_ERROR_ZERO_RETURN)
846 {
847 // ZERO_RETURN means the peer's close_notify WAS received (an
848 // announced close), unlike the terminal branch's empty-queue
849 // fallback below, which means an unannounced one; report eof,
850 // not stream_truncated. A received close_notify queues no
851 // output, so a plain done skips the flush.
852 61x return {engine_want::done, make_error_code(capy::error::eof), 0};
853 }
854
855 32x std::error_code ec;
856
2/2
✓ Branch 55 → 56 taken 1 time.
✓ Branch 55 → 66 taken 31 times.
32x if (op == engine_op::shutdown)
857 {
858
1/1
✓ Branch 56 → 57 taken 1 time.
1x unsigned long ssl_err = ERR_get_error();
859
1/4
✗ Branch 57 → 58 not taken.
✓ Branch 57 → 64 taken 1 time.
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 64 not taken.
1x if (ssl_err == 0 && err == SSL_ERROR_SYSCALL)
860 {
861 // The socket closed without an OpenSSL-level error, but
862 // that can mean either the peer's close_notify already
863 // arrived (this operation's fill, or a concurrent reader,
864 // consumed it) or the peer vanished mid-shutdown without
865 // ever sending one; only RECEIVED_SHUTDOWN tells them
866 // apart, and the documented contract promises
867 // stream_truncated for the latter, matching the read path
868 // and the driver's `map_fill_error` policy.
869 ec = received_shutdown()
870 ? std::error_code{}
871 : make_error_code(capy::error::stream_truncated);
872 }
873 else
874 {
875 1x ec = make_openssl_error(ssl_err);
876 }
877 }
878 else
879 {
880 // SYSCALL and every other terminal code map the same way;
881 // map_openssl_fatal() covers both.
882 31x ec = map_openssl_fatal();
883 }
884 return {
885
3/3
✓ Branch 67 → 68 taken 32 times.
✓ Branch 68 → 69 taken 22 times.
✓ Branch 68 → 70 taken 10 times.
32x pending_output() > 0 ? engine_want::output_then_done
886 : engine_want::done,
887 32x ec, 0};
888 }
889
890 std::size_t
891 55x engine::put_input(unsigned char const* data, std::size_t len)
892 {
893 55x int put = BIO_write(ext_bio_, data, static_cast<int>(len));
894 55x return put > 0 ? static_cast<std::size_t>(put) : 0;
895 }
896
897 std::pair<unsigned char*, std::size_t>
898 32718x engine::input_area()
899 {
900 // A BIO pair hands out a pointer into its own buffer, so the
901 // transport reads ciphertext directly into the pair with no staging
902 // copy. The buffer is circular: this is only the contiguous run to
903 // the wrap, which the driver's read loop already tolerates.
904 32718x char* p = nullptr;
905
1/1
✓ Branch 2 → 3 taken 32718 times.
32718x int const cap = BIO_nwrite0(ext_bio_, &p);
906
2/4
✓ Branch 3 → 4 taken 32718 times.
✗ Branch 3 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 32718 times.
32718x if (cap <= 0 || !p)
907 return {nullptr, 0};
908 32718x return {reinterpret_cast<unsigned char*>(p), static_cast<std::size_t>(cap)};
909 }
910
911 void
912 31770x engine::input_committed(std::size_t n)
913 {
914
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 31770 times.
31770x if (n == 0)
915 return;
916 // The bytes were written straight into the region BIO_nwrite0
917 // returned; advance the pair's write cursor to make them readable.
918 31770x char* p = nullptr;
919
1/1
✓ Branch 4 → 5 taken 31770 times.
31770x BIO_nwrite(ext_bio_, &p, static_cast<int>(n));
920 }
921
922 std::size_t
923 227611x engine::pending_output() const
924 {
925 227611x return BIO_ctrl_pending(ext_bio_);
926 }
927
928 std::size_t
929 33083x engine::get_output(unsigned char* data, std::size_t len)
930 {
931 33083x int r = BIO_read(ext_bio_, data, static_cast<int>(len));
932 33083x return r > 0 ? static_cast<std::size_t>(r) : 0;
933 }
934
935 bool
936 1054x engine::received_shutdown() const
937 {
938 1054x return (SSL_get_shutdown(ssl_) & SSL_RECEIVED_SHUTDOWN) != 0;
939 }
940
941 } // namespace openssl
942
943 } // namespace detail
944
945 } // namespace boost::corosio
946