src/openssl/src/detail/engine.cpp

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