Coverage Report

Created: 2026-09-04 07:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/proto_fuzzer/tls_mock_server.cc
Line
Count
Source
1
/*
2
 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3
 *
4
 * SPDX-License-Identifier: curl
5
 */
6
7
/// @file
8
/// @brief Nonblocking OpenSSL server transport for structured HTTPS inputs.
9
10
#include "proto_fuzzer/tls_mock_server.h"
11
12
#include <openssl/ech.h>
13
#include <openssl/err.h>
14
#include <openssl/pem.h>
15
#include <openssl/ssl.h>
16
#include <sys/socket.h>
17
18
#include <cstddef>
19
#include <string>
20
21
#include "proto_fuzzer/tls_test_credentials.h"
22
23
namespace proto_fuzzer {
24
25
namespace {
26
27
/// Isolate the server and curl client even though both OpenSSL instances run
28
/// on one thread. SSL_get_error requires an empty queue before its I/O call;
29
/// clearing at both boundaries also prevents a server failure from changing
30
/// curl's subsequent error classification.
31
class OpenSslErrorQueueGuard {
32
 public:
33
328k
  OpenSslErrorQueueGuard() { ERR_clear_error(); }
34
35
328k
  ~OpenSslErrorQueueGuard() { ERR_clear_error(); }
36
37
  OpenSslErrorQueueGuard(const OpenSslErrorQueueGuard&) = delete;
38
  OpenSslErrorQueueGuard& operator=(const OpenSslErrorQueueGuard&) = delete;
39
};
40
41
/// Select the one protocol owned by this peer. A fixed server preference keeps
42
/// ordinary HTTPS scripts on HTTP/1.1 while allowing the proxy lane to prove
43
/// curl installed its HTTP/2 connection filter after TLS.
44
int SelectAlpn(SSL* /*ssl*/, const unsigned char** selected, unsigned char* selected_length,
45
28.0k
               const unsigned char* client_protocols, unsigned int client_protocols_length, void* userdata) {
46
28.0k
  const auto protocol = *static_cast<const TlsApplicationProtocol*>(userdata);
47
  // OpenSSL may return a pointer into the server preference list. Function-
48
  // local static storage keeps that result valid for the rest of the
49
  // handshake without reintroducing mutable transport policy as a global.
50
28.0k
  static constexpr unsigned char http11_alpn[] = {8, 'h', 't', 't', 'p', '/', '1', '.', '1'};
51
28.0k
  static constexpr unsigned char http2_alpn[] = {2, 'h', '2'};
52
28.0k
  const unsigned char* server_protocols = protocol == TlsApplicationProtocol::kHttp2 ? http2_alpn : http11_alpn;
53
28.0k
  const unsigned int server_protocols_length =
54
28.0k
      protocol == TlsApplicationProtocol::kHttp2 ? sizeof(http2_alpn) : sizeof(http11_alpn);
55
28.0k
  unsigned char* match = nullptr;
56
28.0k
  unsigned char match_length = 0;
57
28.0k
  const int result = SSL_select_next_proto(&match, &match_length, server_protocols, server_protocols_length,
58
28.0k
                                           client_protocols, client_protocols_length);
59
28.0k
  if (result != OPENSSL_NPN_NEGOTIATED) {
60
2.19k
    return SSL_TLSEXT_ERR_NOACK;
61
2.19k
  }
62
25.8k
  *selected = match;
63
25.8k
  *selected_length = match_length;
64
25.8k
  return SSL_TLSEXT_ERR_OK;
65
28.0k
}
66
67
}  // namespace
68
69
/// Own the certificate, key, session cache, ALPN policy, and observations
70
/// shared by every bounded connection in one Scenario. Keeping both session
71
/// state and its measurements here makes redirects related while preventing
72
/// one fuzz input from affecting the next.
73
class TlsServerContext {
74
 public:
75
  TlsServerContext(TlsApplicationProtocol protocol, curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain)
76
23.3k
      : context_(nullptr),
77
23.3k
        protocol_(protocol),
78
23.3k
        negotiated_tls_version_(0),
79
23.3k
        completed_handshake_count_(0),
80
23.3k
        reused_session_count_(0),
81
23.3k
        write_retry_count_(0),
82
#ifndef OPENSSL_NO_ECH
83
23.3k
        ech_status_(SSL_ECH_STATUS_NOT_TRIED) {
84
#else
85
        ech_status_(-1) {
86
#endif
87
23.3k
    OpenSslErrorQueueGuard error_guard;
88
23.3k
    context_ = SSL_CTX_new(TLS_server_method());
89
23.3k
    if (context_ == nullptr || !LoadCredentials(certificate_chain) || !LoadEchConfig()) {
90
0
      SSL_CTX_free(context_);
91
0
      context_ = nullptr;
92
0
      return;
93
0
    }
94
95
23.3k
    (void)SSL_CTX_set_min_proto_version(context_, TLS1_2_VERSION);
96
23.3k
    (void)SSL_CTX_set_options(context_, SSL_OP_NO_COMPRESSION);
97
23.3k
    (void)SSL_CTX_set_session_cache_mode(context_, SSL_SESS_CACHE_SERVER);
98
23.3k
    constexpr unsigned char session_id_context[] = "curl-fuzzer";
99
23.3k
    (void)SSL_CTX_set_session_id_context(context_, session_id_context, sizeof(session_id_context) - 1);
100
23.3k
    SSL_CTX_set_alpn_select_cb(context_, &SelectAlpn, &protocol_);
101
23.3k
  }
102
103
23.3k
  ~TlsServerContext() {
104
23.3k
    OpenSslErrorQueueGuard error_guard;
105
23.3k
    SSL_CTX_free(context_);
106
23.3k
  }
107
108
  TlsServerContext(const TlsServerContext&) = delete;
109
  TlsServerContext& operator=(const TlsServerContext&) = delete;
110
111
  /// @return the configured context, or nullptr when credential setup failed.
112
28.8k
  SSL_CTX* get() const { return context_; }
113
114
  /// Retain only scalar handshake results; SSL itself remains connection-owned.
115
  /// @param ssl connection that has just completed SSL_accept.
116
27.6k
  void RecordHandshake(SSL* ssl) {
117
27.6k
    if (ssl == nullptr) {
118
0
      return;
119
0
    }
120
27.6k
    negotiated_tls_version_ = SSL_version(ssl);
121
27.6k
    const unsigned char* alpn = nullptr;
122
27.6k
    unsigned int alpn_length = 0;
123
27.6k
    SSL_get0_alpn_selected(ssl, &alpn, &alpn_length);
124
27.6k
    if (alpn != nullptr && alpn_length != 0) {
125
25.3k
      negotiated_alpn_.assign(reinterpret_cast<const char*>(alpn), alpn_length);
126
25.3k
    } else {
127
2.37k
      negotiated_alpn_.clear();
128
2.37k
    }
129
27.6k
    ++completed_handshake_count_;
130
27.6k
    if (SSL_session_reused(ssl) == 1) {
131
4.72k
      ++reused_session_count_;
132
4.72k
    }
133
27.6k
#ifndef OPENSSL_NO_ECH
134
27.6k
    char* inner_name = nullptr;
135
27.6k
    char* outer_name = nullptr;
136
27.6k
    ech_status_ = SSL_ech_get1_status(ssl, &inner_name, &outer_name);
137
27.6k
    ech_inner_name_ = inner_name == nullptr ? std::string() : inner_name;
138
27.6k
    ech_outer_name_ = outer_name == nullptr ? std::string() : outer_name;
139
27.6k
    OPENSSL_free(inner_name);
140
27.6k
    OPENSSL_free(outer_name);
141
27.6k
#endif
142
27.6k
  }
143
144
  /// Record that OpenSSL requires an identical application-write retry.
145
0
  void RecordWriteRetry() { ++write_retry_count_; }
146
147
  /// @return protocol version from the most recent completed handshake.
148
0
  int negotiated_tls_version() const { return negotiated_tls_version_; }
149
  /// @return number of connections that completed their handshake.
150
0
  std::size_t completed_handshake_count() const { return completed_handshake_count_; }
151
  /// @return number of completed handshakes that reused a session.
152
0
  std::size_t reused_session_count() const { return reused_session_count_; }
153
  /// @return number of application writes that OpenSSL asked to retry.
154
0
  std::size_t write_retry_count() const { return write_retry_count_; }
155
  /// @return ALPN protocol from the most recent completed handshake.
156
0
  const std::string& negotiated_alpn() const { return negotiated_alpn_; }
157
  /// @return fixed application protocol this context offers.
158
27.5k
  TlsApplicationProtocol protocol() const { return protocol_; }
159
  /// @return OpenSSL ECH result from the latest completed handshake.
160
0
  int ech_status() const { return ech_status_; }
161
  /// @return latest decrypted inner SNI, if ECH was attempted.
162
0
  const std::string& ech_inner_name() const { return ech_inner_name_; }
163
  /// @return latest public outer SNI, if ECH was attempted.
164
0
  const std::string& ech_outer_name() const { return ech_outer_name_; }
165
166
 private:
167
  /// Append one profile-selected peer certificate without requiring it to
168
  /// authenticate the TLS handshake. OpenSSL transfers ownership on success;
169
  /// failure leaves cleanup with the caller.
170
90
  bool AddExtraChainCertificate(const char* certificate_pem) {
171
90
    BIO* certificate_bio = BIO_new_mem_buf(certificate_pem, -1);
172
90
    if (certificate_bio == nullptr) {
173
0
      return false;
174
0
    }
175
90
    X509* certificate = PEM_read_bio_X509(certificate_bio, nullptr, nullptr, nullptr);
176
90
    BIO_free(certificate_bio);
177
90
    if (certificate == nullptr) {
178
0
      return false;
179
0
    }
180
90
    if (SSL_CTX_add_extra_chain_cert(context_, certificate) != 1) {
181
0
      X509_free(certificate);
182
0
      return false;
183
0
    }
184
90
    return true;
185
90
  }
186
187
  /// Parse the checked-in test-only PEM values entirely in memory.
188
23.3k
  bool LoadCredentials(curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain) {
189
23.3k
    BIO* certificate_bio = BIO_new_mem_buf(tls_test_credentials::kCertificatePem, -1);
190
23.3k
    BIO* key_bio = BIO_new_mem_buf(tls_test_credentials::kPrivateKeyPem, -1);
191
23.3k
    if (certificate_bio == nullptr || key_bio == nullptr) {
192
0
      BIO_free(certificate_bio);
193
0
      BIO_free(key_bio);
194
0
      return false;
195
0
    }
196
197
23.3k
    X509* certificate = PEM_read_bio_X509(certificate_bio, nullptr, nullptr, nullptr);
198
23.3k
    EVP_PKEY* key = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr, nullptr);
199
23.3k
    BIO_free(certificate_bio);
200
23.3k
    BIO_free(key_bio);
201
23.3k
    if (certificate == nullptr || key == nullptr) {
202
0
      X509_free(certificate);
203
0
      EVP_PKEY_free(key);
204
0
      return false;
205
0
    }
206
207
23.3k
    const bool loaded = SSL_CTX_use_certificate(context_, certificate) == 1 &&
208
23.3k
                        SSL_CTX_use_PrivateKey(context_, key) == 1 && SSL_CTX_check_private_key(context_) == 1;
209
23.3k
    X509_free(certificate);
210
23.3k
    EVP_PKEY_free(key);
211
23.3k
    if (!loaded) {
212
0
      return false;
213
0
    }
214
215
23.3k
    switch (certificate_chain) {
216
30
      case curl::fuzzer::proto::TLS_CERTIFICATE_CHAIN_ALL_KEY_TYPES:
217
30
        return AddExtraChainCertificate(tls_test_credentials::kRsaCertificatePem) &&
218
30
               AddExtraChainCertificate(tls_test_credentials::kDsaCertificatePem) &&
219
30
               AddExtraChainCertificate(tls_test_credentials::kDhCertificatePem);
220
23.3k
      case curl::fuzzer::proto::TLS_CERTIFICATE_CHAIN_DEFAULT_EC:
221
23.3k
      default:
222
23.3k
        return true;
223
23.3k
    }
224
23.3k
  }
225
226
  /// Load a fixed test-only ECH private key and matching public config. This
227
  /// makes a successful encrypted ClientHello deterministic without DNS or
228
  /// filesystem state; builds configured without ECH retain the TLS mock.
229
23.3k
  bool LoadEchConfig() {
230
23.3k
#ifndef OPENSSL_NO_ECH
231
23.3k
    BIO* ech_bio = BIO_new_mem_buf(tls_test_credentials::kEchConfigPem, -1);
232
23.3k
    OSSL_ECHSTORE* store = OSSL_ECHSTORE_new(nullptr, nullptr);
233
23.3k
    if (ech_bio == nullptr || store == nullptr) {
234
0
      BIO_free(ech_bio);
235
0
      OSSL_ECHSTORE_free(store);
236
0
      return false;
237
0
    }
238
23.3k
    const bool loaded =
239
23.3k
        OSSL_ECHSTORE_read_pem(store, ech_bio, OSSL_ECH_NO_RETRY) == 1 && SSL_CTX_set1_echstore(context_, store) == 1;
240
23.3k
    BIO_free(ech_bio);
241
23.3k
    OSSL_ECHSTORE_free(store);
242
23.3k
    return loaded;
243
#else
244
    return true;
245
#endif
246
23.3k
  }
247
248
  SSL_CTX* context_;
249
  TlsApplicationProtocol protocol_;
250
  std::string negotiated_alpn_;
251
  int negotiated_tls_version_;
252
  std::size_t completed_handshake_count_;
253
  std::size_t reused_session_count_;
254
  std::size_t write_retry_count_;
255
  int ech_status_;
256
  std::string ech_inner_name_;
257
  std::string ech_outer_name_;
258
};
259
260
namespace {
261
262
/// TLS-aware MockConnection. Application writes are queued before the client
263
/// starts, then encrypted only after SSL_accept has completed. This preserves
264
/// MockServer's useful initial_response semantics without ever putting those
265
/// plaintext bytes onto the TLS wire.
266
class TlsMockConnection final : public MockConnection {
267
 public:
268
  explicit TlsMockConnection(TlsServerContext* context)
269
28.8k
      : ssl_(nullptr),
270
28.8k
        context_(context),
271
28.8k
        pending_offset_(0),
272
28.8k
        pending_write_end_(0),
273
28.8k
        handshake_complete_(false),
274
28.8k
        shutdown_requested_(false),
275
28.8k
        write_shutdown_(false),
276
28.8k
        failed_(false) {
277
28.8k
    OpenSslErrorQueueGuard error_guard;
278
28.8k
    SSL_CTX* ssl_context = context_ == nullptr ? nullptr : context_->get();
279
28.8k
    ssl_ = ssl_context == nullptr ? nullptr : SSL_new(ssl_context);
280
28.8k
    if (ssl_ != nullptr) {
281
28.8k
      SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
282
28.8k
      if (SSL_set_fd(ssl_, server_fd()) != 1) {
283
0
        SSL_free(ssl_);
284
0
        ssl_ = nullptr;
285
28.8k
      } else {
286
28.8k
        SSL_set_accept_state(ssl_);
287
28.8k
      }
288
28.8k
    }
289
28.8k
  }
290
291
28.8k
  ~TlsMockConnection() override {
292
28.8k
    OpenSslErrorQueueGuard error_guard;
293
28.8k
    if (ssl_ != nullptr) {
294
28.8k
      SSL_free(ssl_);
295
28.8k
    }
296
28.8k
  }
297
298
  /// Require both the socketpair and its OpenSSL wrapper to be usable.
299
254k
  bool ok() const override { return MockConnection::ok() && ssl_ != nullptr; }
300
301
  /// Queue bounded application bytes until the handshake can encrypt them.
302
59.1k
  bool WriteAll(const unsigned char* data, std::size_t size) override {
303
59.1k
    if (failed_ || (data == nullptr && size != 0) || size > pending_plaintext_.max_size() - pending_plaintext_.size()) {
304
213
      return false;
305
213
    }
306
58.9k
    if (size != 0) {
307
58.9k
      pending_plaintext_.append(reinterpret_cast<const char*>(data), size);
308
58.9k
    }
309
58.9k
    return true;
310
59.1k
  }
311
312
  /// Advance the handshake, consume encrypted request records, flush queued
313
  /// response records, and finish a requested close without ever waiting.
314
  /// @return decrypted/application bytes plus state transitions made this turn.
315
225k
  std::size_t DrainIncoming() override {
316
225k
    if (!ok() || failed_) {
317
1.75k
      return 0;
318
1.75k
    }
319
320
223k
    OpenSslErrorQueueGuard error_guard;
321
223k
    std::size_t progress = 0;
322
223k
    if (!handshake_complete_) {
323
85.7k
      const int state_before = static_cast<int>(SSL_get_state(ssl_));
324
85.7k
      const int result = SSL_accept(ssl_);
325
      // SSL_get_error must be the next OpenSSL call after failed I/O. Even a
326
      // state query in between would make its use formally unreliable.
327
85.7k
      const int error = result == 1 ? SSL_ERROR_NONE : SSL_get_error(ssl_, result);
328
85.7k
      const int state_after = static_cast<int>(SSL_get_state(ssl_));
329
85.7k
      if (state_after != state_before) {
330
56.0k
        ++progress;
331
56.0k
      }
332
85.7k
      if (result == 1) {
333
27.6k
        handshake_complete_ = true;
334
27.6k
        if (context_ != nullptr) {
335
27.6k
          context_->RecordHandshake(ssl_);
336
27.6k
        }
337
27.6k
        ++progress;
338
58.0k
      } else {
339
58.0k
        if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
340
57.8k
          return progress;
341
57.8k
        }
342
161
        failed_ = true;
343
161
        return progress;
344
58.0k
      }
345
85.7k
    }
346
347
    // A WANT result leaves SSL_write_ex in flight. OpenSSL requires the next
348
    // I/O operation to retry that exact write, so do not interpose SSL_read_ex
349
    // merely because the outer driver has yielded back to us.
350
165k
    if (pending_write_end_ != 0) {
351
0
      progress += FlushPendingWrites();
352
0
      if (failed_ || pending_write_end_ != 0) {
353
0
        return progress;
354
0
      }
355
0
    }
356
357
165k
    unsigned char request[4096];
358
232k
    while (true) {
359
232k
      std::size_t received = 0;
360
232k
      const int result = SSL_read_ex(ssl_, request, sizeof(request), &received);
361
232k
      if (result == 1 && received != 0) {
362
67.2k
        progress += received;
363
67.2k
        continue;
364
67.2k
      }
365
165k
      if (result != 1) {
366
165k
        const int error = SSL_get_error(ssl_, result);
367
165k
        if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE && error != SSL_ERROR_ZERO_RETURN) {
368
91
          failed_ = true;
369
91
        }
370
165k
      }
371
165k
      break;
372
232k
    }
373
374
165k
    progress += FlushPendingWrites();
375
376
165k
    if (!failed_ && shutdown_requested_ && pending_plaintext_.empty() && !write_shutdown_) {
377
21.2k
      const int result = SSL_shutdown(ssl_);
378
21.2k
      if (result >= 0) {
379
        // The first successful call sends close_notify. The fuzzer does not
380
        // need to wait for the client's reciprocal alert before exposing EOF.
381
21.2k
        (void)::shutdown(server_fd(), SHUT_WR);
382
21.2k
        write_shutdown_ = true;
383
21.2k
        ++progress;
384
21.2k
      } else {
385
0
        const int error = SSL_get_error(ssl_, result);
386
0
        if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE) {
387
0
          failed_ = true;
388
0
        }
389
0
      }
390
21.2k
    }
391
165k
    return progress;
392
165k
  }
393
394
  /// Defer close_notify until every queued plaintext byte has become a record.
395
27.5k
  void ShutdownWrite() override {
396
    // An HTTP/2 proxy connection outlives the scripted origin response inside
397
    // its CONNECT stream. Half-closing TLS when the last script chunk is
398
    // queued would make curl observe a dead proxy rather than exercise its
399
    // own stream/session shutdown and GOAWAY handling.
400
27.5k
    if (context_ != nullptr && context_->protocol() == TlsApplicationProtocol::kHttp2) {
401
5.18k
      return;
402
5.18k
    }
403
22.3k
    shutdown_requested_ = true;
404
22.3k
    (void)DrainIncoming();
405
22.3k
  }
406
407
 private:
408
  /// Flush queued response bytes while preserving any OpenSSL retry boundary.
409
  /// @return plaintext bytes OpenSSL accepted during this call.
410
165k
  std::size_t FlushPendingWrites() {
411
165k
    std::size_t progress = 0;
412
213k
    while (!failed_ && pending_offset_ < pending_plaintext_.size()) {
413
47.6k
      if (pending_write_end_ == 0) {
414
        // Freeze one write boundary until OpenSSL accepts it. WriteAll may
415
        // append the next scripted chunk after WANT_READ/WANT_WRITE, but the
416
        // retry contract permits only pointer relocation—not changed bytes or
417
        // length—for the outstanding SSL_write_ex call.
418
47.6k
        pending_write_end_ = pending_plaintext_.size();
419
47.6k
      }
420
47.6k
      std::size_t written = 0;
421
47.6k
      const int result = SSL_write_ex(ssl_, pending_plaintext_.data() + pending_offset_,
422
47.6k
                                      pending_write_end_ - pending_offset_, &written);
423
47.6k
      if (result == 1) {
424
47.6k
        if (written == 0) {
425
0
          failed_ = true;
426
0
          break;
427
0
        }
428
47.6k
        pending_offset_ += written;
429
47.6k
        progress += written;
430
47.6k
        if (pending_offset_ == pending_write_end_) {
431
47.6k
          pending_write_end_ = 0;
432
47.6k
        }
433
47.6k
        continue;
434
47.6k
      }
435
0
      const int error = SSL_get_error(ssl_, result);
436
0
      if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
437
0
        if (context_ != nullptr) {
438
0
          context_->RecordWriteRetry();
439
0
        }
440
0
      } else {
441
0
        failed_ = true;
442
0
      }
443
0
      break;
444
47.6k
    }
445
165k
    if (pending_offset_ == pending_plaintext_.size()) {
446
165k
      pending_plaintext_.clear();
447
165k
      pending_offset_ = 0;
448
165k
      pending_write_end_ = 0;
449
165k
    }
450
165k
    return progress;
451
165k
  }
452
453
  SSL* ssl_;
454
  TlsServerContext* context_;
455
  std::string pending_plaintext_;
456
  std::size_t pending_offset_;
457
  /// Exclusive end of a write that OpenSSL may require us to retry exactly.
458
  std::size_t pending_write_end_;
459
  bool handshake_complete_;
460
  bool shutdown_requested_;
461
  bool write_shutdown_;
462
  bool failed_;
463
};
464
465
}  // namespace
466
467
/// Build one reusable in-process server context per fuzz iteration.
468
0
TlsMockServer::TlsMockServer() : TlsMockServer(curl::fuzzer::proto::TLS_CERTIFICATE_CHAIN_DEFAULT_EC) {}
469
470
/// Select one bounded certificate chain while retaining HTTP/1.1 ALPN.
471
TlsMockServer::TlsMockServer(curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain)
472
17.8k
    : TlsMockServer(TlsApplicationProtocol::kHttp11, certificate_chain) {}
473
474
/// Construct the shared TLS transport with one fixed ALPN outcome.
475
TlsMockServer::TlsMockServer(TlsApplicationProtocol protocol,
476
                             curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain)
477
23.3k
    : context_(std::make_unique<TlsServerContext>(protocol, certificate_chain)), saw_live_tls_session_(false) {}
478
479
/// Release SSL objects before their owning server context. OpenSSL reference
480
/// counting makes the reverse order legal, but making the ownership order
481
/// explicit keeps future transport state from acquiring a hidden dependency.
482
23.3k
TlsMockServer::~TlsMockServer() { ResetConnections(); }
483
484
/// Add verification to the common socket callbacks. Curl copies the blob
485
/// descriptor during setopt and borrows the inline certificate bytes, whose
486
/// program lifetime safely exceeds every easy handle.
487
17.8k
void TlsMockServer::Install(CURL* easy) {
488
17.8k
  MockServer::Install(easy);
489
17.8k
  struct curl_blob trust_anchor = {const_cast<char*>(tls_test_credentials::kCertificatePem),
490
17.8k
                                   sizeof(tls_test_credentials::kCertificatePem) - 1, CURL_BLOB_NOCOPY};
491
17.8k
  (void)curl_easy_setopt(easy, CURLOPT_CAINFO_BLOB, &trust_anchor);
492
17.8k
  (void)curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 1L);
493
17.8k
  (void)curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, 2L);
494
17.8k
}
495
496
/// Report whether the drive reached curl's live TLS backend-query path.
497
0
bool TlsMockServer::saw_live_tls_session() const { return saw_live_tls_session_; }
498
499
/// Report the protocol selected by the latest successful server handshake.
500
0
int TlsMockServer::negotiated_tls_version() const {
501
0
  return context_ == nullptr ? 0 : context_->negotiated_tls_version();
502
0
}
503
504
/// Report how many bounded connections finished their TLS handshake.
505
0
std::size_t TlsMockServer::completed_handshake_count() const {
506
0
  return context_ == nullptr ? 0 : context_->completed_handshake_count();
507
0
}
508
509
/// Report how many later connections resumed state from this scenario.
510
0
std::size_t TlsMockServer::reused_session_count() const {
511
0
  return context_ == nullptr ? 0 : context_->reused_session_count();
512
0
}
513
514
/// Report how often response delivery reached OpenSSL's exact-retry path.
515
0
std::size_t TlsMockServer::write_retry_count() const { return context_ == nullptr ? 0 : context_->write_retry_count(); }
516
517
/// Return the latest negotiated application protocol without exposing SSL.
518
0
std::string TlsMockServer::negotiated_alpn() const {
519
0
  return context_ == nullptr ? std::string() : context_->negotiated_alpn();
520
0
}
521
522
/// Return OpenSSL's latest ECH result without exposing its SSL object.
523
0
int TlsMockServer::ech_status() const { return context_ == nullptr ? -1 : context_->ech_status(); }
524
525
/// Return the SNI that OpenSSL recovered from the encrypted ClientHello.
526
0
std::string TlsMockServer::ech_inner_name() const {
527
0
  return context_ == nullptr ? std::string() : context_->ech_inner_name();
528
0
}
529
530
/// Return the public SNI that remained in the outer ClientHello.
531
0
std::string TlsMockServer::ech_outer_name() const {
532
0
  return context_ == nullptr ? std::string() : context_->ech_outer_name();
533
0
}
534
535
/// Create the polymorphic record-layer connection consumed by MockServer.
536
28.8k
std::unique_ptr<MockConnection> TlsMockServer::CreateConnection() {
537
28.8k
  return std::make_unique<TlsMockConnection>(context_.get());
538
28.8k
}
539
540
/// Query while the connection filters are attached. Stop after the first live
541
/// result so these coverage probes add a bounded handful of calls during the
542
/// handshake rather than recurring throughout every HTTP parser iteration.
543
/// @param easy Active easy handle whose connection filters remain attached.
544
140k
void TlsMockServer::ObserveActiveTransfer(CURL* easy) {
545
140k
  if (saw_live_tls_session_) {
546
47.6k
    return;
547
47.6k
  }
548
92.6k
  long verify_result = 0;
549
92.6k
  curl_off_t appconnect_time = 0;
550
92.6k
  struct curl_certinfo* certificate_info = nullptr;
551
92.6k
  struct curl_tlssessioninfo* tls_session = nullptr;
552
92.6k
  (void)curl_easy_getinfo(easy, CURLINFO_SSL_VERIFYRESULT, &verify_result);
553
92.6k
  (void)curl_easy_getinfo(easy, CURLINFO_APPCONNECT_TIME_T, &appconnect_time);
554
92.6k
  (void)curl_easy_getinfo(easy, CURLINFO_CERTINFO, &certificate_info);
555
92.6k
  if (curl_easy_getinfo(easy, CURLINFO_TLS_SSL_PTR, &tls_session) == CURLE_OK && tls_session != nullptr &&
556
92.6k
      tls_session->internals != nullptr) {
557
16.6k
    saw_live_tls_session_ = true;
558
16.6k
  }
559
92.6k
}
560
561
}  // namespace proto_fuzzer