curl-fuzzer C++ reference
Loading...
Searching...
No Matches
tls_mock_server.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3 *
4 * SPDX-License-Identifier: curl
5 */
6
9
11
12#include <openssl/bio.h>
13#include <openssl/ech.h>
14#include <openssl/err.h>
15#include <openssl/ssl.h>
16#include <sys/socket.h>
17
18#include <cstddef>
19#include <cstring>
20#include <string>
21#include <vector>
22
24#include "proto_fuzzer/tls_test_credentials.h"
25
26namespace proto_fuzzer {
27
28namespace {
29
30constexpr std::size_t kMaxSessionExportAttempts = 8;
31constexpr std::size_t kMaxSessionHashSize = 128;
32constexpr std::size_t kMaxSessionDataSize = 16 * 1024;
33
37bool CurlClientUsesOpenSsl() {
38 const curl_version_info_data* version = curl_version_info(CURLVERSION_NOW);
39 constexpr char kOpenSslPrefix[] = "OpenSSL/";
40 return version != nullptr && version->ssl_version != nullptr &&
41 std::strncmp(version->ssl_version, kOpenSslPrefix, sizeof(kOpenSslPrefix) - 1) == 0;
42}
43
46struct ExportedSession {
47 std::vector<unsigned char> salted_hash;
48 std::vector<unsigned char> session_data;
49};
50
53CURLcode CaptureExportedSession(CURL* /*easy*/, void* user_data, const char* /*session_key*/,
54 const unsigned char* salted_hash, std::size_t salted_hash_size,
55 const unsigned char* session_data, std::size_t session_data_size,
56 curl_off_t /*valid_until*/, int /*ietf_tls_id*/, const char* /*alpn*/,
57 std::size_t /*early_data_max*/) {
58 auto* exported = static_cast<ExportedSession*>(user_data);
59 if (!exported->session_data.empty() || salted_hash == nullptr || salted_hash_size == 0 ||
60 salted_hash_size > kMaxSessionHashSize || session_data == nullptr || session_data_size == 0 ||
61 session_data_size > kMaxSessionDataSize) {
62 return CURLE_OK;
63 }
64 exported->salted_hash.assign(salted_hash, salted_hash + salted_hash_size);
65 exported->session_data.assign(session_data, session_data + session_data_size);
66 return CURLE_OK;
67}
68
73class OpenSslErrorQueueGuard {
74 public:
75 OpenSslErrorQueueGuard() { ERR_clear_error(); }
76
77 ~OpenSslErrorQueueGuard() { ERR_clear_error(); }
78
79 OpenSslErrorQueueGuard(const OpenSslErrorQueueGuard&) = delete;
80 OpenSslErrorQueueGuard& operator=(const OpenSslErrorQueueGuard&) = delete;
81};
82
86int SelectAlpn(SSL* /*ssl*/, const unsigned char** selected, unsigned char* selected_length,
87 const unsigned char* client_protocols, unsigned int client_protocols_length, void* userdata) {
88 const auto protocol = *static_cast<const TlsApplicationProtocol*>(userdata);
89 // OpenSSL may return a pointer into the server preference list. Function-
90 // local static storage keeps that result valid for the rest of the
91 // handshake without reintroducing mutable transport policy as a global.
92 static constexpr unsigned char http11_alpn[] = {8, 'h', 't', 't', 'p', '/', '1', '.', '1'};
93 static constexpr unsigned char http2_alpn[] = {2, 'h', '2'};
94 const unsigned char* server_protocols = protocol == TlsApplicationProtocol::kHttp2 ? http2_alpn : http11_alpn;
95 const unsigned int server_protocols_length =
96 protocol == TlsApplicationProtocol::kHttp2 ? sizeof(http2_alpn) : sizeof(http11_alpn);
97 unsigned char* match = nullptr;
98 unsigned char match_length = 0;
99 const int result = SSL_select_next_proto(&match, &match_length, server_protocols, server_protocols_length,
100 client_protocols, client_protocols_length);
101 if (result != OPENSSL_NPN_NEGOTIATED) {
102 return SSL_TLSEXT_ERR_NOACK;
103 }
104 *selected = match;
105 *selected_length = match_length;
106 return SSL_TLSEXT_ERR_OK;
107}
108
109} // namespace
110
115class TlsServerContext {
116 public:
117 // Doxygen's no-preprocessing pass would see both conditional body openings.
118 // Skip this implementation-only constructor to preserve the enclosing scope.
120 TlsServerContext(TlsApplicationProtocol protocol, curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain,
121 TlsGroupPolicy group_policy)
122 : context_(nullptr),
123 protocol_(protocol),
124 group_policy_(group_policy),
125 negotiated_tls_version_(0),
126 completed_handshake_count_(0),
127 reused_session_count_(0),
128 write_retry_count_(0),
129#ifndef OPENSSL_NO_ECH
130 ech_status_(SSL_ECH_STATUS_NOT_TRIED) {
131#else
132 ech_status_(-1) {
133#endif
134 OpenSslErrorQueueGuard error_guard;
135 context_ = SSL_CTX_new(TLS_server_method());
136 if (context_ == nullptr || !ApplyGroupPolicy() || !InstallCachedTlsTestCredentials(context_, certificate_chain) ||
137 !LoadEchConfig()) {
138 SSL_CTX_free(context_);
139 context_ = nullptr;
140 return;
141 }
142
143 (void)SSL_CTX_set_min_proto_version(context_, TLS1_2_VERSION);
144 (void)SSL_CTX_set_options(context_, SSL_OP_NO_COMPRESSION);
145 (void)SSL_CTX_set_session_cache_mode(context_, SSL_SESS_CACHE_SERVER);
146 constexpr unsigned char session_id_context[] = "curl-fuzzer";
147 (void)SSL_CTX_set_session_id_context(context_, session_id_context, sizeof(session_id_context) - 1);
148 SSL_CTX_set_alpn_select_cb(context_, &SelectAlpn, &protocol_);
149 }
151
152 ~TlsServerContext() {
153 OpenSslErrorQueueGuard error_guard;
154 SSL_CTX_free(context_);
155 }
156
157 TlsServerContext(const TlsServerContext&) = delete;
158 TlsServerContext& operator=(const TlsServerContext&) = delete;
159
161 SSL_CTX* get() const { return context_; }
162
165 void RecordHandshake(SSL* ssl) {
166 if (ssl == nullptr) {
167 return;
168 }
169 negotiated_tls_version_ = SSL_version(ssl);
170 const unsigned char* alpn = nullptr;
171 unsigned int alpn_length = 0;
172 SSL_get0_alpn_selected(ssl, &alpn, &alpn_length);
173 if (alpn != nullptr && alpn_length != 0) {
174 negotiated_alpn_.assign(reinterpret_cast<const char*>(alpn), alpn_length);
175 } else {
176 negotiated_alpn_.clear();
177 }
178 ++completed_handshake_count_;
179 if (SSL_session_reused(ssl) == 1) {
180 ++reused_session_count_;
181 }
182 const int negotiated_group = SSL_get_negotiated_group(ssl);
183 const char* group_name = negotiated_group == 0 ? nullptr : SSL_group_to_name(ssl, negotiated_group);
184 negotiated_group_ = group_name == nullptr ? std::string() : group_name;
185#ifndef OPENSSL_NO_ECH
186 char* inner_name = nullptr;
187 char* outer_name = nullptr;
188 ech_status_ = SSL_ech_get1_status(ssl, &inner_name, &outer_name);
189 ech_inner_name_ = inner_name == nullptr ? std::string() : inner_name;
190 ech_outer_name_ = outer_name == nullptr ? std::string() : outer_name;
191 OPENSSL_free(inner_name);
192 OPENSSL_free(outer_name);
193#endif
194 }
195
197 void RecordWriteRetry() { ++write_retry_count_; }
198
200 int negotiated_tls_version() const { return negotiated_tls_version_; }
202 std::size_t completed_handshake_count() const { return completed_handshake_count_; }
204 std::size_t reused_session_count() const { return reused_session_count_; }
206 std::size_t write_retry_count() const { return write_retry_count_; }
208 const std::string& negotiated_alpn() const { return negotiated_alpn_; }
210 const std::string& negotiated_group() const { return negotiated_group_; }
212 TlsApplicationProtocol protocol() const { return protocol_; }
214 int ech_status() const { return ech_status_; }
216 const std::string& ech_inner_name() const { return ech_inner_name_; }
218 const std::string& ech_outer_name() const { return ech_outer_name_; }
219
220 private:
223 bool ApplyGroupPolicy() {
224 switch (group_policy_) {
225 case TlsGroupPolicy::kProviderDefault:
226 return true;
227 case TlsGroupPolicy::kX25519:
228 return SSL_CTX_set1_groups_list(context_, "X25519") == 1;
229 }
230 return false;
231 }
232
236 bool LoadEchConfig() {
237#ifndef OPENSSL_NO_ECH
238 BIO* ech_bio = BIO_new_mem_buf(tls_test_credentials::kEchConfigPem, -1);
239 OSSL_ECHSTORE* store = OSSL_ECHSTORE_new(nullptr, nullptr);
240 if (ech_bio == nullptr || store == nullptr) {
241 BIO_free(ech_bio);
242 OSSL_ECHSTORE_free(store);
243 return false;
244 }
245 const bool loaded =
246 OSSL_ECHSTORE_read_pem(store, ech_bio, OSSL_ECH_NO_RETRY) == 1 && SSL_CTX_set1_echstore(context_, store) == 1;
247 BIO_free(ech_bio);
248 OSSL_ECHSTORE_free(store);
249 return loaded;
250#else
251 return true;
252#endif
253 }
254
255 SSL_CTX* context_;
256 TlsApplicationProtocol protocol_;
257 const TlsGroupPolicy group_policy_;
258 std::string negotiated_alpn_;
259 std::string negotiated_group_;
260 int negotiated_tls_version_;
261 std::size_t completed_handshake_count_;
262 std::size_t reused_session_count_;
263 std::size_t write_retry_count_;
264 int ech_status_;
265 std::string ech_inner_name_;
266 std::string ech_outer_name_;
267};
268
269namespace {
270
275class TlsMockConnection final : public MockConnection {
276 public:
277 explicit TlsMockConnection(TlsServerContext* context)
278 : ssl_(nullptr),
279 context_(context),
280 pending_offset_(0),
281 pending_write_end_(0),
282 handshake_complete_(false),
283 shutdown_requested_(false),
284 write_shutdown_(false),
285 failed_(false) {
286 InitializeSsl();
287 }
288
289 TlsMockConnection(TlsServerContext* context, int accepted_fd)
290 : MockConnection(accepted_fd),
291 ssl_(nullptr),
292 context_(context),
293 pending_offset_(0),
294 pending_write_end_(0),
295 handshake_complete_(false),
296 shutdown_requested_(false),
297 write_shutdown_(false),
298 failed_(false) {
299 InitializeSsl();
300 }
301
302 ~TlsMockConnection() override {
303 OpenSslErrorQueueGuard error_guard;
304 if (ssl_ != nullptr) {
305 SSL_free(ssl_);
306 }
307 }
308
310 bool ok() const override { return MockConnection::ok() && ssl_ != nullptr; }
311
313 bool WriteAll(const unsigned char* data, std::size_t size) override {
314 if (failed_ || (data == nullptr && size != 0) || size > pending_plaintext_.max_size() - pending_plaintext_.size()) {
315 return false;
316 }
317 if (size != 0) {
318 pending_plaintext_.append(reinterpret_cast<const char*>(data), size);
319 }
320 return true;
321 }
322
326 std::size_t DrainIncoming() override {
327 if (!ok() || failed_) {
328 return 0;
329 }
330
331 OpenSslErrorQueueGuard error_guard;
332 std::size_t progress = 0;
333 if (!handshake_complete_) {
334 const int state_before = static_cast<int>(SSL_get_state(ssl_));
335 const int result = SSL_accept(ssl_);
336 // SSL_get_error must be the next OpenSSL call after failed I/O. Even a
337 // state query in between would make its use formally unreliable.
338 const int error = result == 1 ? SSL_ERROR_NONE : SSL_get_error(ssl_, result);
339 const int state_after = static_cast<int>(SSL_get_state(ssl_));
340 if (state_after != state_before) {
341 ++progress;
342 }
343 if (result == 1) {
344 handshake_complete_ = true;
345 if (context_ != nullptr) {
346 context_->RecordHandshake(ssl_);
347 }
348 ++progress;
349 } else {
350 if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
351 return progress;
352 }
353 failed_ = true;
354 return progress;
355 }
356 }
357
358 // A WANT result leaves SSL_write_ex in flight. OpenSSL requires the next
359 // I/O operation to retry that exact write, so do not interpose SSL_read_ex
360 // merely because the outer driver has yielded back to us.
361 if (pending_write_end_ != 0) {
362 progress += FlushPendingWrites();
363 if (failed_ || pending_write_end_ != 0) {
364 return progress;
365 }
366 }
367
368 unsigned char request[4096];
369 while (true) {
370 std::size_t received = 0;
371 const int result = SSL_read_ex(ssl_, request, sizeof(request), &received);
372 if (result == 1 && received != 0) {
373 NotifyIncomingData(request, received);
374 progress += received;
375 continue;
376 }
377 if (result != 1) {
378 const int error = SSL_get_error(ssl_, result);
379 if (error == SSL_ERROR_ZERO_RETURN) {
380 // The client sent close_notify. TLS requires a reciprocal close_notify
381 // before the orderly shutdown completes; protocols such as FTP upload
382 // wait for that completion even though the application data is done.
383 shutdown_requested_ = true;
384 } else if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE) {
385 failed_ = true;
386 }
387 }
388 break;
389 }
390
391 progress += FlushPendingWrites();
392
393 if (!failed_ && shutdown_requested_ && pending_plaintext_.empty() && !write_shutdown_) {
394 const int result = SSL_shutdown(ssl_);
395 if (result >= 0) {
396 // The first successful call sends close_notify. The fuzzer does not
397 // need to wait for the client's reciprocal alert before exposing EOF.
398 (void)::shutdown(server_fd(), SHUT_WR);
399 write_shutdown_ = true;
400 ++progress;
401 } else {
402 const int error = SSL_get_error(ssl_, result);
403 if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE) {
404 failed_ = true;
405 }
406 }
407 }
408 return progress;
409 }
410
412 void ShutdownWrite() override {
413 // An HTTP/2 proxy connection outlives the scripted origin response inside
414 // its CONNECT stream. Half-closing TLS when the last script chunk is
415 // queued would make curl observe a dead proxy rather than exercise its
416 // own stream/session shutdown and GOAWAY handling.
417 if (context_ != nullptr && context_->protocol() == TlsApplicationProtocol::kHttp2) {
418 return;
419 }
420 shutdown_requested_ = true;
421 (void)DrainIncoming();
422 }
423
424 private:
426 void InitializeSsl() {
427 OpenSslErrorQueueGuard error_guard;
428 SSL_CTX* ssl_context = context_ == nullptr ? nullptr : context_->get();
429 ssl_ = ssl_context == nullptr ? nullptr : SSL_new(ssl_context);
430 if (ssl_ == nullptr) {
431 return;
432 }
433 SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
434 if (SSL_set_fd(ssl_, server_fd()) != 1) {
435 SSL_free(ssl_);
436 ssl_ = nullptr;
437 return;
438 }
439 SSL_set_accept_state(ssl_);
440 }
441
444 std::size_t FlushPendingWrites() {
445 std::size_t progress = 0;
446 while (!failed_ && pending_offset_ < pending_plaintext_.size()) {
447 if (pending_write_end_ == 0) {
448 // Freeze one write boundary until OpenSSL accepts it. WriteAll may
449 // append the next scripted chunk after WANT_READ/WANT_WRITE, but the
450 // retry contract permits only pointer relocation—not changed bytes or
451 // length—for the outstanding SSL_write_ex call.
452 pending_write_end_ = pending_plaintext_.size();
453 }
454 std::size_t written = 0;
455 const int result = SSL_write_ex(ssl_, pending_plaintext_.data() + pending_offset_,
456 pending_write_end_ - pending_offset_, &written);
457 if (result == 1) {
458 if (written == 0) {
459 failed_ = true;
460 break;
461 }
462 pending_offset_ += written;
463 progress += written;
464 if (pending_offset_ == pending_write_end_) {
465 pending_write_end_ = 0;
466 }
467 continue;
468 }
469 const int error = SSL_get_error(ssl_, result);
470 if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
471 if (context_ != nullptr) {
472 context_->RecordWriteRetry();
473 }
474 } else {
475 failed_ = true;
476 }
477 break;
478 }
479 if (pending_offset_ == pending_plaintext_.size()) {
480 pending_plaintext_.clear();
481 pending_offset_ = 0;
482 pending_write_end_ = 0;
483 }
484 return progress;
485 }
486
487 SSL* ssl_;
488 TlsServerContext* context_;
489 std::string pending_plaintext_;
490 std::size_t pending_offset_;
492 std::size_t pending_write_end_;
493 bool handshake_complete_;
494 bool shutdown_requested_;
495 bool write_shutdown_;
496 bool failed_;
497};
498
502class TlsCloseNotifyConnection final : public MockConnection {
503 public:
504 TlsCloseNotifyConnection() : alert_sent_(false) {}
505
506 bool WriteAll(const unsigned char* /*data*/, std::size_t /*size*/) override { return true; }
507
508 void ShutdownWrite() override {
509 if (alert_sent_) {
510 return;
511 }
512 static constexpr unsigned char kCloseNotify[] = {
513 0x15, 0x03, 0x03, 0x00, 0x02, 0x01, 0x00,
514 };
515 alert_sent_ = true;
516 (void)MockConnection::WriteAll(kCloseNotify, sizeof(kCloseNotify));
517 MockConnection::ShutdownWrite();
518 }
519
520 private:
521 bool alert_sent_;
522};
523
528class TlsHelloRequestConnection final : public MockConnection {
529 public:
530 TlsHelloRequestConnection() : record_sent_(false) {}
531
532 bool WriteAll(const unsigned char* /*data*/, std::size_t /*size*/) override { return SendOnce(); }
533
534 void ShutdownWrite() override {
535 (void)SendOnce();
536 MockConnection::ShutdownWrite();
537 }
538
539 private:
540 bool SendOnce() {
541 if (record_sent_) {
542 return true;
543 }
544 static constexpr unsigned char kHelloRequest[] = {
545 0x16, 0x03, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
546 };
547 record_sent_ = MockConnection::WriteAll(kHelloRequest, sizeof(kHelloRequest));
548 return record_sent_;
549 }
550
551 bool record_sent_;
552};
553
554} // namespace
555
562 curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain,
564 curl::fuzzer::proto::TlsHandshakeProfile handshake_profile)
565 : group_policy_(group_policy),
566 openssl_close_notify_(handshake_profile == curl::fuzzer::proto::TLS_HANDSHAKE_OPENSSL_CLOSE_NOTIFY &&
567 CurlClientUsesOpenSsl()),
568 openssl_hello_request_(handshake_profile == curl::fuzzer::proto::TLS_HANDSHAKE_OPENSSL_HELLO_REQUEST &&
569 CurlClientUsesOpenSsl()),
570 context_(std::make_unique<TlsServerContext>(protocol, certificate_chain, group_policy)) {}
571
572TlsMockTransport::~TlsMockTransport() = default;
573
577void TlsMockTransport::Install(CURL* easy) const {
578 struct curl_blob trust_anchor = {const_cast<char*>(tls_test_credentials::kCertificatePem),
579 sizeof(tls_test_credentials::kCertificatePem) - 1, CURL_BLOB_NOCOPY};
580 (void)curl_easy_setopt(easy, CURLOPT_CAINFO_BLOB, &trust_anchor);
581 (void)curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 1L);
582 (void)curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, 2L);
583}
584
587std::unique_ptr<MockConnection> TlsMockTransport::CreateConnection() {
588 if (openssl_close_notify_) {
589 return std::make_unique<TlsCloseNotifyConnection>();
590 }
591 if (openssl_hello_request_) {
592 return std::make_unique<TlsHelloRequestConnection>();
593 }
594 return std::make_unique<TlsMockConnection>(context_.get());
595}
596
602std::unique_ptr<MockConnection> TlsMockTransport::CreateAcceptedConnection(int accepted_fd) {
603 return std::make_unique<TlsMockConnection>(context_.get(), accepted_fd);
604}
605
608 return context_ == nullptr ? 0 : context_->negotiated_tls_version();
609}
610
613 return context_ == nullptr ? 0 : context_->completed_handshake_count();
614}
615
618 return context_ == nullptr ? 0 : context_->reused_session_count();
619}
620
623 return context_ == nullptr ? 0 : context_->write_retry_count();
624}
625
628 return context_ == nullptr ? std::string() : context_->negotiated_alpn();
629}
630
632TlsGroupPolicy TlsMockTransport::group_policy() const { return group_policy_; }
633
636 return context_ == nullptr ? std::string() : context_->negotiated_group();
637}
638
640int TlsMockTransport::ech_status() const { return context_ == nullptr ? -1 : context_->ech_status(); }
641
644 return context_ == nullptr ? std::string() : context_->ech_inner_name();
645}
646
649 return context_ == nullptr ? std::string() : context_->ech_outer_name();
650}
651
653TlsMockServer::TlsMockServer() : TlsMockServer(curl::fuzzer::proto::TLS_CERTIFICATE_CHAIN_DEFAULT_EC) {}
654
658TlsMockServer::TlsMockServer(curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain,
659 curl::fuzzer::proto::TlsHandshakeProfile handshake_profile)
661 handshake_profile) {}
662
670 curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain,
671 TlsGroupPolicy group_policy, curl::fuzzer::proto::TlsHandshakeProfile handshake_profile)
672 : transport_(protocol, certificate_chain, group_policy, handshake_profile),
673 saw_live_tls_session_(false),
674 session_export_attempt_count_(0),
675 exported_session_count_(0),
676 imported_session_count_(0) {}
677
682
688void TlsMockServer::Install(CURL* easy) {
690 transport_.Install(easy);
691}
692
694bool TlsMockServer::saw_live_tls_session() const { return saw_live_tls_session_; }
695
698std::size_t TlsMockServer::exported_session_count() const { return exported_session_count_; }
699
702std::size_t TlsMockServer::imported_session_count() const { return imported_session_count_; }
703
706int TlsMockServer::negotiated_tls_version() const { return transport_.negotiated_tls_version(); }
707
709std::size_t TlsMockServer::completed_handshake_count() const { return transport_.completed_handshake_count(); }
710
712std::size_t TlsMockServer::reused_session_count() const { return transport_.reused_session_count(); }
713
715std::size_t TlsMockServer::write_retry_count() const { return transport_.write_retry_count(); }
716
718std::string TlsMockServer::negotiated_alpn() const { return transport_.negotiated_alpn(); }
719
721TlsGroupPolicy TlsMockServer::group_policy() const { return transport_.group_policy(); }
722
724std::string TlsMockServer::negotiated_group() const { return transport_.negotiated_group(); }
725
728int TlsMockServer::ech_status() const { return transport_.ech_status(); }
729
731std::string TlsMockServer::ech_inner_name() const { return transport_.ech_inner_name(); }
732
734std::string TlsMockServer::ech_outer_name() const { return transport_.ech_outer_name(); }
735
739std::unique_ptr<MockConnection> TlsMockServer::CreateConnection() { return transport_.CreateConnection(); }
740
746 if (!saw_live_tls_session_) {
747 long verify_result = 0;
748 curl_off_t appconnect_time = 0;
749 struct curl_certinfo* certificate_info = nullptr;
750 struct curl_tlssessioninfo* tls_session = nullptr;
751 (void)curl_easy_getinfo(easy, CURLINFO_SSL_VERIFYRESULT, &verify_result);
752 (void)curl_easy_getinfo(easy, CURLINFO_APPCONNECT_TIME_T, &appconnect_time);
753 (void)curl_easy_getinfo(easy, CURLINFO_CERTINFO, &certificate_info);
754 if (curl_easy_getinfo(easy, CURLINFO_TLS_SSL_PTR, &tls_session) == CURLE_OK && tls_session != nullptr &&
755 tls_session->internals != nullptr) {
756 saw_live_tls_session_ = true;
757 }
758 }
759
760 // A TLS 1.3 ticket may arrive after the peer first completes its handshake.
761 // Retry only a small fixed number of outer-loop observations, and stop
762 // permanently once one copied ticket has made one import attempt.
763 const bool completed_live_handshake = transport_.completed_handshake_count() != 0;
764#if defined(PROTO_FUZZER_OBSERVE_STATE)
765 // Read the owned peer observations once after a real handshake. Signed
766 // status values retain their unavailable sentinels; string results are
767 // consumed immediately while their temporaries and context are still live.
768 static bool observed = false;
769 if (completed_live_handshake && !observed) {
770 observed = true;
771 const volatile bool live = saw_live_tls_session();
772 const volatile int status[] = {negotiated_tls_version(), ech_status()};
773 const volatile std::size_t counts[] = {
776 negotiated_group().size(), ech_inner_name().size(), ech_outer_name().size()};
777 (void)live;
778 (void)status;
779 (void)counts;
780 }
781#endif
782 if (!completed_live_handshake || exported_session_count_ != 0 ||
783 session_export_attempt_count_ >= kMaxSessionExportAttempts) {
784 return;
785 }
786 ++session_export_attempt_count_;
787 ExportedSession exported;
788 const CURLcode export_result = curl_easy_ssls_export(easy, &CaptureExportedSession, &exported);
789 if (export_result != CURLE_OK || exported.session_data.empty()) {
790 return;
791 }
792 ++exported_session_count_;
793
794 // curl holds the SSL session-cache lock throughout CaptureExportedSession.
795 // This deliberately separate call therefore exercises import without
796 // recursive API use or retaining any callback-owned pointer.
797 if (curl_easy_ssls_import(easy, nullptr, exported.salted_hash.data(), exported.salted_hash.size(),
798 exported.session_data.data(), exported.session_data.size()) == CURLE_OK) {
799 ++imported_session_count_;
800 }
801}
802
803} // namespace proto_fuzzer
virtual void Install(CURL *easy)
Install the common OPENSOCKETFUNCTION / OPENSOCKETDATA / SOCKOPTFUNCTION callbacks on 'easy'.
void ResetConnections()
Release all current and retired socketpairs before resetting transport- specific state.
void Install(CURL *easy) override
Install common socket callbacks and a stable local trust anchor.
std::string ech_inner_name() const
std::size_t reused_session_count() const
std::size_t imported_session_count() const
std::size_t exported_session_count() const
std::string negotiated_alpn() const
TlsMockServer()
Build one reusable in-process server context per fuzz iteration.
std::size_t completed_handshake_count() const
TlsGroupPolicy group_policy() const
~TlsMockServer() override
Release SSL objects before their owning server context.
std::string negotiated_group() const
void ObserveActiveTransfer(CURL *easy) override
Query while the connection filters are attached.
std::size_t write_retry_count() const
std::unique_ptr< MockConnection > CreateConnection() override
Wrap one socketpair server endpoint in an OpenSSL acceptor consumed by MockServer.
std::string ech_outer_name() const
std::unique_ptr< MockConnection > CreateConnection()
Wrap one socketpair server endpoint in an OpenSSL acceptor.
std::unique_ptr< MockConnection > CreateAcceptedConnection(int accepted_fd)
Wrap one accepted loopback descriptor in this transport's OpenSSL server context.
std::size_t completed_handshake_count() const
std::size_t reused_session_count() const
void Install(CURL *easy) const
Install the stable local trust anchor and strict peer verification.
std::size_t write_retry_count() const
TlsMockTransport(TlsApplicationProtocol protocol=TlsApplicationProtocol::kHttp11, curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain=curl::fuzzer::proto::TLS_CERTIFICATE_CHAIN_DEFAULT_EC, TlsGroupPolicy group_policy=TlsGroupPolicy::kProviderDefault, curl::fuzzer::proto::TlsHandshakeProfile handshake_profile=curl::fuzzer::proto::TLS_HANDSHAKE_DEFAULT)
Construct one reusable TLS context for a protocol-specific peer.
std::string negotiated_alpn() const
TlsGroupPolicy group_policy() const
std::string negotiated_group() const
Reusable TLS transport for structured in-process protocol peers.
TlsGroupPolicy
Key-exchange group policy fixed when a TLS peer is constructed.
@ kProviderDefault
Retain the linked OpenSSL provider's preferred group ordering.
TlsApplicationProtocol
Application protocol selected by the in-process TLS peer.
@ kHttp2
The dedicated proxy peer exchanges HTTP/2 frames after its handshake.
@ kHttp11
Ordinary HTTPS scripts contain HTTP/1.1 response bytes.
bool InstallCachedTlsTestCredentials(SSL_CTX *context, curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain)
Install references to immutable, process-lifetime test credentials in one fresh TLS context.
Installs cached test credentials into fresh TLS contexts.