src/openssl/src/detail/engine.cpp

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