src/openssl/src/detail/engine.cpp

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