src/openssl/src/detail/engine.cpp

87.9% Lines (401/456) 100.0% List of functions (35/35) 62.5% Branches (280/448)
engine.cpp
f(x) Functions (35)
Function Calls Lines Branches Blocks
boost::corosio::(anonymous namespace)::tls_method_compat() :33 2796x 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 2125x 80.0% 78.6% 83.0% boost::corosio::(anonymous namespace)::openssl_proto_version(boost::corosio::tls_version) :73 5592x 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 27x 66.7% 50.0% 66.0% boost::corosio::(anonymous namespace)::map_openssl_fatal() :132 26x 83.3% 50.0% 66.0% boost::corosio::openssl_category() :143 34x 100.0% 75.0% 100.0% boost::corosio::detail::password_callback(char*, int, int, void*) :158 1x 83.3% 50.0% 71.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 5592x 90.1% 60.2% 100.0% boost::corosio::detail::openssl_native_context::~openssl_native_context() :618 8388x 100.0% 50.0% 100.0% boost::corosio::detail::get_openssl_native_context(boost::corosio::detail::tls_context_data const&) :626 2802x 100.0% 100.0% boost::corosio::detail::get_openssl_native_context(boost::corosio::detail::tls_context_data const&)::'lambda'()::operator()() const :629 2796x 100.0% 50.0% 50.0% boost::corosio::detail::openssl::engine::~engine() :639 5604x 100.0% 50.0% 100.0% boost::corosio::detail::openssl::engine::init(boost::corosio::tls_context const&) :648 2802x 61.1% 50.0% 62.0% boost::corosio::detail::openssl::engine::reset() :685 65x 54.5% 40.0% 50.0% boost::corosio::detail::openssl::engine::context_setup_failed() const :716 2130x 100.0% 100.0% boost::corosio::detail::openssl::engine::check_context() const :722 2130x 100.0% 100.0% 100.0% boost::corosio::detail::openssl::engine::check_session() const :730 2125x 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&) :738 2125x 75.0% 75.0% 80.0% boost::corosio::detail::openssl::engine::apply_hostname(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&) :762 2125x 100.0% 100.0% boost::corosio::detail::openssl::engine::apply_alpn_offer() :768 1418x 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 :784 1050x 100.0% 75.0% 100.0% boost::corosio::detail::openssl::engine::perform(boost::corosio::detail::engine_op, void*, unsigned long) :794 345433x 91.5% 86.1% 84.0% boost::corosio::detail::openssl::engine::put_input(unsigned char const*, unsigned long) :891 55x 100.0% 50.0% 75.0% boost::corosio::detail::openssl::engine::input_area() :898 23491x 85.7% 50.0% 80.0% boost::corosio::detail::openssl::engine::input_committed(unsigned long) :912 22473x 83.3% 50.0% 75.0% boost::corosio::detail::openssl::engine::pending_output() const :923 758190x 100.0% 100.0% boost::corosio::detail::openssl::engine::get_output(unsigned char*, unsigned long) :929 142920x 100.0% 100.0% 100.0% boost::corosio::detail::openssl::engine::received_shutdown() const :936 1053x 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 2796x tls_method_compat() noexcept
34 {
35 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
36
1/2
✓ Branch 0 taken 2796 times.
✗ Branch 1 not taken.
2796x return TLS_method();
37 #else
38 return SSLv23_method();
39 #endif
40 }
41
42 inline bool
43 2125x 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 2112 times.
✓ Branch 1 taken 13 times.
2125x 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 2112 times.
✓ Branch 1 taken 13 times.
2125x bool const is_ip = name && detail::is_ip_literal(hostname);
55
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2123 times.
2125x char const* dns_name = is_ip ? nullptr : name;
56
57
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2125 times.
2125x if (SSL_set_tlsext_host_name(ssl, dns_name) != 1)
58 return false;
59
60 2125x auto* param = SSL_get0_param(ssl);
61
1/2
✓ Branch 0 taken 2125 times.
✗ Branch 1 not taken.
2125x if (!param)
62 return name == nullptr;
63
64
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2125 times.
2125x 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 2123 times.
2125x if (is_ip)
67 2x return X509_VERIFY_PARAM_set1_ip_asc(param, name) == 1;
68 2123x return X509_VERIFY_PARAM_set1_ip(param, nullptr, 0) == 1;
69 2125x }
70
71 // Map a portable protocol version to the OpenSSL version constant.
72 inline int
73 5592x openssl_proto_version(tls_version v) noexcept
74 {
75 5592x 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 27x make_openssl_error(unsigned long err) noexcept
118 {
119
2/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 27 times.
27x 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 27x return std::error_code(static_cast<int>(err), openssl_category());
123 27x }
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 26x map_openssl_fatal() noexcept
133 {
134
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26x unsigned long ssl_err = ERR_get_error();
135
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26x if (ssl_err == 0)
136 return make_error_code(capy::error::stream_truncated);
137 26x return make_openssl_error(ssl_err);
138 26x }
139
140 } // namespace
141
142 std::error_category const&
143 34x openssl_category() noexcept
144 {
145
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
34x static openssl_category_impl instance;
146 34x return instance;
147 }
148
149 //
150 // Native context caching
151 //
152
153 namespace detail {
154
155 static int sni_ctx_data_index = -1;
156
157 static int
158 1x password_callback(char* buf, int size, int rwflag, void* userdata)
159 {
160 1x auto* cd = static_cast<tls_context_data const*>(userdata);
161
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
1x if (!cd || !cd->password_callback)
162 return 0;
163
164 1x tls_password_purpose purpose = (rwflag == 0)
165 ? tls_password_purpose::for_reading
166 : tls_password_purpose::for_writing;
167
168 std::string password =
169 1x cd->password_callback(static_cast<std::size_t>(size), purpose);
170
171 1x int len = static_cast<int>(password.size());
172
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x if (len > size)
173 len = size;
174
175 1x std::memcpy(buf, password.data(), static_cast<std::size_t>(len));
176 1x return len;
177 1x }
178
179 // Trampoline installed via SSL_CTX_set_verify. Recovers the portable
180 // context data from the SSL_CTX ex_data (populated for every context)
181 // and applies, in order: the revocation policy's soft-fail downgrade,
182 // then the user's verify callback. Installed whenever a verify callback
183 // or a non-disabled revocation policy is configured.
184 static int
185 10x verify_callback_trampoline(int preverified, X509_STORE_CTX* store_ctx)
186 {
187 10x SSL* ssl = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(
188 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 2796x 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 8388x explicit openssl_native_context(tls_context_data const& cd)
320 2796x : ctx_(nullptr)
321 2796x , cd_(&cd)
322 5592x {
323
1/2
✓ Branch 0 taken 2796 times.
✗ Branch 1 not taken.
2796x ctx_ = SSL_CTX_new(tls_method_compat());
324
1/2
✓ Branch 0 taken 2796 times.
✗ Branch 1 not taken.
2796x if (!ctx_)
325 return;
326
327
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2791 times.
2796x 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 2796 times.
✗ Branch 1 not taken.
2796x SSL_CTX_set_ex_data(
332 2796x ctx_, sni_ctx_data_index, const_cast<tls_context_data*>(&cd));
333
334
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2788 times.
2796x 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 2792 times.
2796x 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 2796 times.
✗ Branch 1 not taken.
2796x SSL_CTX_set_mode(ctx_, SSL_MODE_ENABLE_PARTIAL_WRITE);
349
1/2
✓ Branch 0 taken 2796 times.
✗ Branch 1 not taken.
2796x SSL_CTX_set_mode(ctx_, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
350 #if defined(SSL_MODE_RELEASE_BUFFERS)
351
1/2
✓ Branch 0 taken 2796 times.
✗ Branch 1 not taken.
2796x 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 2795 times.
✓ Branch 1 taken 1 time.
2796x if (cd.min_version > cd.max_version)
358 1x setup_failed_ = true;
359
2/4
✓ Branch 0 taken 2796 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2796 times.
✗ Branch 3 not taken.
2796x 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 2796 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2796 times.
✗ Branch 3 not taken.
2796x if (!SSL_CTX_set_max_proto_version(
363 ctx_, openssl_proto_version(cd.max_version)))
364 setup_failed_ = true;
365
366 2796x int verify_mode_flag = SSL_VERIFY_NONE;
367
2/2
✓ Branch 0 taken 1395 times.
✓ Branch 1 taken 1401 times.
2796x if (cd.verification_mode == tls_verify_mode::peer)
368 1395x verify_mode_flag = SSL_VERIFY_PEER;
369
2/2
✓ Branch 0 taken 1398 times.
✓ Branch 1 taken 3 times.
1401x 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 2796x bool const need_trampoline =
375
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2791 times.
2796x cd.verify_callback ||
376 2791x cd.revocation != tls_revocation_policy::disabled;
377
1/2
✓ Branch 0 taken 2796 times.
✗ Branch 1 not taken.
2796x SSL_CTX_set_verify(
378 2796x ctx_, verify_mode_flag,
379 2796x 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 2791 times.
2796x 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 not taken.
✓ Branch 1 taken 5 times.
5x if (!p12)
400 setup_failed_ = true;
401 else
402 {
403 5x EVP_PKEY* pkey = nullptr;
404 5x X509* cert = nullptr;
405 5x STACK_OF(X509)* chain = nullptr;
406
3/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 2 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 2791 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 1394 times.
✓ Branch 3 taken 1397 times.
2796x if (cd.pkcs12_data.empty() && !cd.entity_certificate.empty())
444 {
445
1/2
✓ Branch 0 taken 1394 times.
✗ Branch 1 not taken.
1394x BIO* bio = BIO_new_mem_buf(
446 1394x cd.entity_certificate.data(),
447 1394x static_cast<int>(cd.entity_certificate.size()));
448
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1394 times.
1394x if (bio)
449 {
450 1394x X509* cert = nullptr;
451
1/2
✓ Branch 0 taken 1394 times.
✗ Branch 1 not taken.
1394x if (cd.entity_cert_format == tls_file_format::pem)
452
1/2
✓ Branch 0 taken 1394 times.
✗ Branch 1 not taken.
1394x cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
453 else
454 cert = d2i_X509_bio(bio, nullptr);
455
2/2
✓ Branch 0 taken 1393 times.
✓ Branch 1 taken 1 time.
1394x if (cert)
456 {
457
1/2
✓ Branch 0 taken 1393 times.
✗ Branch 1 not taken.
1393x SSL_CTX_use_certificate(ctx_, cert);
458
1/2
✓ Branch 0 taken 1393 times.
✗ Branch 1 not taken.
1393x X509_free(cert);
459 1393x }
460
1/2
✓ Branch 0 taken 1394 times.
✗ Branch 1 not taken.
1394x BIO_free(bio);
461 1394x }
462 1394x }
463
464
4/4
✓ Branch 0 taken 2791 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 2790 times.
2796x if (cd.pkcs12_data.empty() && !cd.certificate_chain.empty())
465 {
466
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x BIO* bio = BIO_new_mem_buf(
467 1x cd.certificate_chain.data(),
468 1x static_cast<int>(cd.certificate_chain.size()));
469
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x if (bio)
470 {
471 1x X509* entity =
472
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
473
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x if (entity)
474 {
475
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x SSL_CTX_use_certificate(ctx_, entity);
476
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x X509_free(entity);
477 1x }
478
479 X509* cert;
480
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(
481 2x bio, nullptr, nullptr, nullptr)) != nullptr)
482 {
483
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x SSL_CTX_add_extra_chain_cert(ctx_, cert);
484 }
485
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x ERR_clear_error();
486
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x BIO_free(bio);
487 1x }
488 1x }
489
490
4/4
✓ Branch 0 taken 2791 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 1395 times.
✓ Branch 3 taken 1396 times.
2796x if (cd.pkcs12_data.empty() && !cd.private_key.empty())
491 {
492
1/2
✓ Branch 0 taken 1395 times.
✗ Branch 1 not taken.
1395x BIO* bio = BIO_new_mem_buf(
493 1395x cd.private_key.data(), static_cast<int>(cd.private_key.size()));
494
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1395 times.
1395x if (bio)
495 {
496 1395x EVP_PKEY* pkey = nullptr;
497
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1395 times.
1395x if (cd.private_key_format == tls_file_format::pem)
498 {
499
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1394 times.
1395x if (cd.password_callback)
500
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x pkey = PEM_read_bio_PrivateKey(
501 1x bio, nullptr, password_callback,
502 1x const_cast<tls_context_data*>(&cd));
503 else
504
1/2
✓ Branch 0 taken 1394 times.
✗ Branch 1 not taken.
1394x pkey = PEM_read_bio_PrivateKey(
505 1394x bio, nullptr, nullptr, nullptr);
506 1395x }
507 else
508 pkey = d2i_PrivateKey_bio(bio, nullptr);
509
2/2
✓ Branch 0 taken 1394 times.
✓ Branch 1 taken 1 time.
1395x if (pkey)
510 {
511
1/2
✓ Branch 0 taken 1394 times.
✗ Branch 1 not taken.
1394x SSL_CTX_use_PrivateKey(ctx_, pkey);
512
1/2
✓ Branch 0 taken 1394 times.
✗ Branch 1 not taken.
1394x EVP_PKEY_free(pkey);
513 1394x }
514
1/2
✓ Branch 0 taken 1395 times.
✗ Branch 1 not taken.
1395x BIO_free(bio);
515 1395x }
516 1395x }
517
518
1/2
✓ Branch 0 taken 2796 times.
✗ Branch 1 not taken.
2796x X509_STORE* store = SSL_CTX_get_cert_store(ctx_);
519
2/2
✓ Branch 0 taken 1407 times.
✓ Branch 1 taken 2796 times.
4203x for (auto const& ca : cd.ca_certificates)
520 {
521 // A trust anchor that fails to parse or add must not pass
522 // silently: the store would verify against fewer anchors than
523 // requested and reject a legitimate peer as untrusted. Fail
524 // closed instead, tolerating only a duplicate the store already
525 // holds.
526
1/2
✓ Branch 0 taken 1407 times.
✗ Branch 1 not taken.
1407x BIO* bio = BIO_new_mem_buf(ca.data(), static_cast<int>(ca.size()));
527
1/2
✓ Branch 0 taken 1407 times.
✗ Branch 1 not taken.
1407x if (!bio)
528 {
529 setup_failed_ = true;
530 continue;
531 }
532
1/2
✓ Branch 0 taken 1407 times.
✗ Branch 1 not taken.
1407x X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
533
1/2
✓ Branch 0 taken 1407 times.
✗ Branch 1 not taken.
1407x if (cert)
534 {
535
2/6
✓ Branch 0 taken 1407 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1407 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1407x if (X509_STORE_add_cert(store, cert) != 1 &&
536 ERR_GET_REASON(ERR_peek_last_error()) !=
537 X509_R_CERT_ALREADY_IN_HASH_TABLE)
538 setup_failed_ = true;
539
1/2
✓ Branch 0 taken 1407 times.
✗ Branch 1 not taken.
1407x X509_free(cert);
540 1407x }
541 else
542 setup_failed_ = true;
543
1/2
✓ Branch 0 taken 1407 times.
✗ Branch 1 not taken.
1407x ERR_clear_error();
544
1/2
✓ Branch 0 taken 1407 times.
✗ Branch 1 not taken.
1407x BIO_free(bio);
545 }
546
547 // Trust anchors from the system store and explicit directories.
548 // Failures leave the affected source unloaded rather than aborting
549 // context creation; the error queue is cleared so it does not leak
550 // into a later handshake.
551
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2795 times.
2796x if (cd.use_default_verify_paths)
552
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x SSL_CTX_set_default_verify_paths(ctx_);
553
2/2
✓ Branch 0 taken 2796 times.
✓ Branch 1 taken 1 time.
2797x for (auto const& path : cd.verify_paths)
554
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x SSL_CTX_load_verify_locations(ctx_, nullptr, path.c_str());
555
1/2
✓ Branch 0 taken 2796 times.
✗ Branch 1 not taken.
2796x ERR_clear_error();
556
557 // Certificate revocation via CRLs. Load any supplied CRLs and, when
558 // a revocation policy is active, enable leaf CRL checking. soft_fail
559 // vs hard_fail is applied in the verify trampoline. CRL_CHECK (leaf
560 // only) is used so a missing CRL for a trusted root is not itself an
561 // error.
562
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2793 times.
2796x if (cd.revocation != tls_revocation_policy::disabled)
563 {
564
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5x for (auto const& crl_data : cd.crls)
565 {
566
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x BIO* bio = BIO_new_mem_buf(
567 2x crl_data.data(), static_cast<int>(crl_data.size()));
568
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x if (!bio)
569 {
570 setup_failed_ = true;
571 continue;
572 }
573 // Accept PEM or DER (the documented contract). Try PEM first,
574 // then rewind and try DER.
575 2x X509_CRL* crl =
576
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x PEM_read_bio_X509_CRL(bio, nullptr, nullptr, nullptr);
577
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2x if (!crl)
578 {
579
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x BIO_reset(bio);
580
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x crl = d2i_X509_CRL_bio(bio, nullptr);
581 1x }
582
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2x if (crl)
583 {
584
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x X509_STORE_add_crl(store, crl);
585
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x X509_CRL_free(crl);
586 1x }
587 else
588 {
589 // A supplied CRL that parses as neither PEM nor DER must
590 // not be silently dropped; record it so the handshake
591 // fails closed rather than weakening revocation.
592 1x setup_failed_ = true;
593 }
594
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x BIO_free(bio);
595 }
596
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3x X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK);
597
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3x ERR_clear_error();
598 3x }
599
600
1/2
✓ Branch 0 taken 2796 times.
✗ Branch 1 not taken.
2796x SSL_CTX_set_verify_depth(ctx_, cd.verify_depth);
601
602 // Cipher configuration. TLS 1.2-and-below use the cipher list;
603 // TLS 1.3 uses the separate ciphersuites API. The security level
604 // is deliberately left at the library default: a weak cipher
605 // string should fail loudly rather than be silently permitted via
606 // a forced @SECLEVEL=0. Callers that genuinely need a lower level
607 // can express it in the cipher string (e.g. "...:@SECLEVEL=0").
608 // A cipher string the library rejects must not silently fall back to
609 // the default suites; fail closed instead.
610
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2792 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
2800x if (!cd.ciphersuites.empty() &&
611
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4x !SSL_CTX_set_cipher_list(ctx_, cd.ciphersuites.c_str()))
612 setup_failed_ = true;
613
4/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2791 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 1 time.
2801x if (!cd.ciphersuites_tls13.empty() &&
614
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5x !SSL_CTX_set_ciphersuites(ctx_, cd.ciphersuites_tls13.c_str()))
615 1x setup_failed_ = true;
616 5592x }
617
618 8388x ~openssl_native_context() override
619 8388x {
620
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2796 times.
2796x if (ctx_)
621
1/2
✓ Branch 0 taken 2796 times.
✗ Branch 1 not taken.
2796x SSL_CTX_free(ctx_);
622 8388x }
623 };
624
625 inline openssl_native_context*
626 2802x get_openssl_native_context(tls_context_data const& cd)
627 {
628 static char key;
629
1/2
✓ Branch 0 taken 2796 times.
✗ Branch 1 not taken.
5598x auto* p = cd.find(&key, [&] { return new openssl_native_context(cd); });
630 2802x return static_cast<openssl_native_context*>(p);
631 }
632
633 //
634 // engine
635 //
636
637 namespace openssl {
638
639 5604x engine::~engine()
640 2802x {
641
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2802 times.
2802x if (ext_bio_)
642
1/2
✓ Branch 0 taken 2802 times.
✗ Branch 1 not taken.
2802x BIO_free(ext_bio_);
643
1/2
✓ Branch 0 taken 2802 times.
✗ Branch 1 not taken.
2802x if (ssl_)
644
1/2
✓ Branch 0 taken 2802 times.
✗ Branch 1 not taken.
2802x SSL_free(ssl_);
645 5604x }
646
647 std::error_code
648 2802x engine::init(tls_context const& ctx)
649 {
650 2802x auto& cd = get_tls_context_data(ctx);
651 2802x nc_ = get_openssl_native_context(cd);
652
1/2
✓ Branch 0 taken 2802 times.
✗ Branch 1 not taken.
2802x if (!nc_->ctx_)
653 {
654 // The cache retains a failed context build permanently (it
655 // never retries), so a later construction can reach here
656 // with an already-drained error queue; make_openssl_error(0)
657 // is falsy and would let the caller treat this as success and
658 // dereference a null ssl_ on first use. Report unconditionally
659 // rather than trust ERR_get_error() to be nonzero.
660 return std::make_error_code(std::errc::not_enough_memory);
661 }
662
663 2802x ssl_ = SSL_new(nc_->ctx_);
664
1/2
✓ Branch 0 taken 2802 times.
✗ Branch 1 not taken.
2802x if (!ssl_)
665 {
666 unsigned long err = ERR_get_error();
667 return make_openssl_error(err);
668 }
669
670 2802x BIO* int_bio = nullptr;
671
1/2
✓ Branch 0 taken 2802 times.
✗ Branch 1 not taken.
2802x if (!BIO_new_bio_pair(&int_bio, 0, &ext_bio_, 0))
672 {
673 unsigned long err = ERR_get_error();
674 SSL_free(ssl_);
675 ssl_ = nullptr;
676 return make_openssl_error(err);
677 }
678
679 2802x SSL_set_bio(ssl_, int_bio, int_bio);
680
681 2802x return {};
682 2802x }
683
684 void
685 65x engine::reset()
686 {
687
1/2
✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
65x if (!ssl_)
688 return;
689
690 // Preserves SSL* and BIO pair, releases session state
691
1/2
✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
65x if (SSL_clear(ssl_) != 1)
692 clear_failed_ = true;
693
694 // SSL_clear() retains the negotiated session so a subsequent
695 // handshake on this SSL* can resume it. Resumed handshakes skip
696 // certificate/hostname re-verification, which would let a changed
697 // set_hostname() go unchecked after reset(); drop it to force a
698 // full handshake. A failure leaves the old session resumable,
699 // defeating that guarantee, so latch the same failure flag
700 // SSL_clear() uses above.
701
1/2
✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
65x if (SSL_set_session(ssl_, nullptr) != 1)
702 clear_failed_ = true;
703
704 // Drain stale data from the external BIO. Mirrors the driver's
705 // flush guard: a read failure here would otherwise spin the loop
706 // forever since the pending count never advances.
707 char drain[1024];
708
1/2
✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
65x while (BIO_ctrl_pending(ext_bio_) > 0)
709 {
710 if (BIO_read(ext_bio_, drain, sizeof(drain)) <= 0)
711 break;
712 }
713 65x }
714
715 bool
716 2130x engine::context_setup_failed() const noexcept
717 {
718 2130x return nc_->setup_failed_;
719 }
720
721 std::error_code
722 2130x engine::check_context() const noexcept
723 {
724
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2125 times.
2130x if (context_setup_failed())
725 5x return std::make_error_code(std::errc::invalid_argument);
726 2125x return {};
727 2130x }
728
729 std::error_code
730 2125x engine::check_session() const noexcept
731 {
732
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2125 times.
2125x if (clear_failed_)
733 return std::make_error_code(std::errc::invalid_argument);
734 2125x return {};
735 2125x }
736
737 std::error_code
738 2125x engine::prepare(tls_context const&, tls_role role, std::string const& hostname)
739 {
740 // The hostname applies to client handshakes only; a server
741 // handshake clears any name left by a prior client-role
742 // handshake so client certificates are never hostname-matched.
743 2125x std::string const no_name;
744
3/4
✓ Branch 0 taken 707 times.
✓ Branch 1 taken 1418 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2125 times.
2125x if (!apply_hostname(role == tls_role::client ? hostname : no_name))
745 {
746 // Fail closed rather than handshake without the requested
747 // name check.
748 return std::make_error_code(std::errc::invalid_argument);
749 }
750
751 // Client offers its ALPN protocol list; the server selects via
752 // the context callback. Role is only known here, so install the
753 // offer per-session; fail closed rather than negotiate nothing
754 // silently.
755
3/4
✓ Branch 0 taken 1418 times.
✓ Branch 1 taken 707 times.
✓ Branch 2 taken 1418 times.
✗ Branch 3 not taken.
2125x if (role == tls_role::client && !apply_alpn_offer())
756 return std::make_error_code(std::errc::invalid_argument);
757
758 2125x return {};
759 2125x }
760
761 bool
762 2125x engine::apply_hostname(std::string const& hostname)
763 {
764 2125x return apply_hostname_verification(ssl_, hostname);
765 }
766
767 bool
768 1418x engine::apply_alpn_offer()
769 {
770
2/2
✓ Branch 0 taken 1416 times.
✓ Branch 1 taken 2 times.
1418x if (nc_->alpn_wire_.empty())
771 1416x return true;
772
773 // SSL_set_alpn_protos uses the inverted convention: 0 = success.
774 // A non-zero return (allocation failure) means the offer was not
775 // installed; the caller fails closed rather than negotiate nothing
776 // silently.
777 4x return SSL_set_alpn_protos(
778 2x ssl_,
779 2x reinterpret_cast<unsigned char const*>(nc_->alpn_wire_.data()),
780 4x static_cast<unsigned int>(nc_->alpn_wire_.size())) == 0;
781 1418x }
782
783 void
784 1050x engine::capture_alpn(std::string& out) const
785 {
786 1050x unsigned char const* data = nullptr;
787 1050x unsigned int len = 0;
788 1050x SSL_get0_alpn_selected(ssl_, &data, &len);
789
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1048 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
1050x if (data && len)
790 2x out.assign(reinterpret_cast<char const*>(data), len);
791 1050x }
792
793 engine_result
794 345433x engine::perform(engine_op op, void* data, std::size_t len)
795 {
796 345433x ERR_clear_error();
797
798 345433x int ret = 0;
799
6/6
✓ Branch 0 taken 3359 times.
✓ Branch 1 taken 2375 times.
✓ Branch 2 taken 160406 times.
✓ Branch 3 taken 139686 times.
✓ Branch 4 taken 39298 times.
✓ Branch 5 taken 309 times.
345433x switch (op)
800 {
801 case engine_op::handshake_client:
802 3359x ret = SSL_connect(ssl_);
803 3359x break;
804 case engine_op::handshake_server:
805 2375x ret = SSL_accept(ssl_);
806 2375x break;
807 case engine_op::read:
808 160406x ret = SSL_read(ssl_, data, static_cast<int>(len));
809 160406x break;
810 case engine_op::write:
811 139686x ret = SSL_write(ssl_, data, static_cast<int>(len));
812 139686x break;
813 case engine_op::shutdown:
814 309x ret = SSL_shutdown(ssl_);
815 309x break;
816 }
817
818
2/2
✓ Branch 0 taken 121108 times.
✓ Branch 1 taken 145729 times.
345433x bool const transfer = op == engine_op::read || op == engine_op::write;
819
820 // SSL_shutdown returning 0 means our close_notify was queued but
821 // the peer's has not arrived yet: flush it, then read for it.
822
4/4
✓ Branch 0 taken 309 times.
✓ Branch 1 taken 266528 times.
✓ Branch 2 taken 218 times.
✓ Branch 3 taken 91 times.
266837x if (op == engine_op::shutdown && ret == 0)
823 273x return {
824 91x pending_output() > 0 ? engine_want::output_then_retry
825 : engine_want::input,
826 91x {}, 0};
827
828
4/4
✓ Branch 0 taken 5952 times.
✓ Branch 1 taken 260794 times.
✓ Branch 2 taken 19649 times.
✓ Branch 3 taken 25601 times.
266746x if (transfer ? ret > 0 : ret == 1)
829 1121772x return {
830 280443x pending_output() > 0 ? engine_want::output_then_done
831 : engine_want::done,
832
4/4
✓ Branch 0 taken 279283 times.
✓ Branch 1 taken 1160 times.
✓ Branch 2 taken 279283 times.
✓ Branch 3 taken 1160 times.
560886x {}, transfer ? static_cast<std::size_t>(ret) : 0};
833
834 25601x int const err = SSL_get_error(ssl_, ret);
835
836
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 25600 times.
25601x if (err == SSL_ERROR_WANT_WRITE)
837 1x return {engine_want::output_then_retry, {}, 0};
838
839
2/2
✓ Branch 0 taken 25518 times.
✓ Branch 1 taken 82 times.
25600x if (err == SSL_ERROR_WANT_READ)
840 76554x return {
841 25518x pending_output() > 0 ? engine_want::output_then_retry
842 : engine_want::input,
843 25518x {}, 0};
844
845
4/4
✓ Branch 0 taken 58 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 55 times.
82x if (transfer && err == SSL_ERROR_ZERO_RETURN)
846 {
847 // ZERO_RETURN means the peer's close_notify WAS received (an
848 // announced close), unlike the terminal branch's empty-queue
849 // fallback below, which means an unannounced one; report eof,
850 // not stream_truncated. A received close_notify queues no
851 // output, so a plain done skips the flush.
852 55x return {engine_want::done, make_error_code(capy::error::eof), 0};
853 }
854
855 27x std::error_code ec;
856
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 26 times.
27x if (op == engine_op::shutdown)
857 {
858 1x unsigned long ssl_err = ERR_get_error();
859
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)
860 {
861 // The socket closed without an OpenSSL-level error, but
862 // that can mean either the peer's close_notify already
863 // arrived (this operation's fill, or a concurrent reader,
864 // consumed it) or the peer vanished mid-shutdown without
865 // ever sending one; only RECEIVED_SHUTDOWN tells them
866 // apart, and the documented contract promises
867 // stream_truncated for the latter, matching the read path
868 // and the driver's `map_fill_error` policy.
869 ec = received_shutdown()
870 ? std::error_code{}
871 : make_error_code(capy::error::stream_truncated);
872 }
873 else
874 {
875 1x ec = make_openssl_error(ssl_err);
876 }
877 1x }
878 else
879 {
880 // SYSCALL and every other terminal code map the same way;
881 // map_openssl_fatal() covers both.
882 26x ec = map_openssl_fatal();
883 }
884 81x return {
885 27x pending_output() > 0 ? engine_want::output_then_done
886 : engine_want::done,
887 27x ec, 0};
888 306135x }
889
890 std::size_t
891 55x engine::put_input(unsigned char const* data, std::size_t len)
892 {
893 55x int put = BIO_write(ext_bio_, data, static_cast<int>(len));
894
1/2
✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
55x return put > 0 ? static_cast<std::size_t>(put) : 0;
895 }
896
897 std::pair<unsigned char*, std::size_t>
898 23491x engine::input_area()
899 {
900 // A BIO pair hands out a pointer into its own buffer, so the
901 // transport reads ciphertext directly into the pair with no staging
902 // copy. The buffer is circular: this is only the contiguous run to
903 // the wrap, which the driver's read loop already tolerates.
904 23491x char* p = nullptr;
905 23491x int const cap = BIO_nwrite0(ext_bio_, &p);
906
2/4
✓ Branch 0 taken 23491 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 23491 times.
23491x if (cap <= 0 || !p)
907 return {nullptr, 0};
908 23491x return {reinterpret_cast<unsigned char*>(p), static_cast<std::size_t>(cap)};
909 23491x }
910
911 void
912 22473x engine::input_committed(std::size_t n)
913 {
914
1/2
✓ Branch 0 taken 22473 times.
✗ Branch 1 not taken.
22473x if (n == 0)
915 return;
916 // The bytes were written straight into the region BIO_nwrite0
917 // returned; advance the pair's write cursor to make them readable.
918 22473x char* p = nullptr;
919 22473x BIO_nwrite(ext_bio_, &p, static_cast<int>(n));
920 22473x }
921
922 std::size_t
923 758190x engine::pending_output() const
924 {
925 758190x return BIO_ctrl_pending(ext_bio_);
926 }
927
928 std::size_t
929 142920x engine::get_output(unsigned char* data, std::size_t len)
930 {
931 142920x int r = BIO_read(ext_bio_, data, static_cast<int>(len));
932
2/2
✓ Branch 0 taken 142917 times.
✓ Branch 1 taken 3 times.
142920x return r > 0 ? static_cast<std::size_t>(r) : 0;
933 }
934
935 bool
936 1053x engine::received_shutdown() const
937 {
938 1053x return (SSL_get_shutdown(ssl_) & SSL_RECEIVED_SHUTDOWN) != 0;
939 }
940
941 } // namespace openssl
942
943 } // namespace detail
944
945 } // namespace boost::corosio
946