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>
24#include "proto_fuzzer/tls_test_credentials.h"
26namespace proto_fuzzer {
30constexpr std::size_t kMaxSessionExportAttempts = 8;
31constexpr std::size_t kMaxSessionHashSize = 128;
32constexpr std::size_t kMaxSessionDataSize = 16 * 1024;
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;
46struct ExportedSession {
47 std::vector<unsigned char> salted_hash;
48 std::vector<unsigned char> session_data;
53CURLcode CaptureExportedSession(CURL* ,
void* user_data,
const char* ,
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 ,
int ,
const char* ,
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) {
64 exported->salted_hash.assign(salted_hash, salted_hash + salted_hash_size);
65 exported->session_data.assign(session_data, session_data + session_data_size);
73class OpenSslErrorQueueGuard {
75 OpenSslErrorQueueGuard() { ERR_clear_error(); }
77 ~OpenSslErrorQueueGuard() { ERR_clear_error(); }
79 OpenSslErrorQueueGuard(
const OpenSslErrorQueueGuard&) =
delete;
80 OpenSslErrorQueueGuard& operator=(
const OpenSslErrorQueueGuard&) =
delete;
86int SelectAlpn(SSL* ,
const unsigned char** selected,
unsigned char* selected_length,
87 const unsigned char* client_protocols,
unsigned int client_protocols_length,
void* userdata) {
92 static constexpr unsigned char http11_alpn[] = {8,
'h',
't',
't',
'p',
'/',
'1',
'.',
'1'};
93 static constexpr unsigned char http2_alpn[] = {2,
'h',
'2'};
95 const unsigned int server_protocols_length =
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;
105 *selected_length = match_length;
106 return SSL_TLSEXT_ERR_OK;
115class TlsServerContext {
120 TlsServerContext(TlsApplicationProtocol protocol, curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain,
121 TlsGroupPolicy group_policy)
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) {
134 OpenSslErrorQueueGuard error_guard;
135 context_ = SSL_CTX_new(TLS_server_method());
138 SSL_CTX_free(context_);
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_);
152 ~TlsServerContext() {
153 OpenSslErrorQueueGuard error_guard;
154 SSL_CTX_free(context_);
157 TlsServerContext(
const TlsServerContext&) =
delete;
158 TlsServerContext& operator=(
const TlsServerContext&) =
delete;
161 SSL_CTX* get()
const {
return context_; }
165 void RecordHandshake(SSL* ssl) {
166 if (ssl ==
nullptr) {
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);
176 negotiated_alpn_.clear();
178 ++completed_handshake_count_;
179 if (SSL_session_reused(ssl) == 1) {
180 ++reused_session_count_;
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);
197 void RecordWriteRetry() { ++write_retry_count_; }
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_; }
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_; }
223 bool ApplyGroupPolicy() {
224 switch (group_policy_) {
225 case TlsGroupPolicy::kProviderDefault:
227 case TlsGroupPolicy::kX25519:
228 return SSL_CTX_set1_groups_list(context_,
"X25519") == 1;
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) {
242 OSSL_ECHSTORE_free(store);
246 OSSL_ECHSTORE_read_pem(store, ech_bio, OSSL_ECH_NO_RETRY) == 1 && SSL_CTX_set1_echstore(context_, store) == 1;
248 OSSL_ECHSTORE_free(store);
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_;
265 std::string ech_inner_name_;
266 std::string ech_outer_name_;
275class TlsMockConnection final :
public MockConnection {
277 explicit TlsMockConnection(TlsServerContext* context)
281 pending_write_end_(0),
282 handshake_complete_(false),
283 shutdown_requested_(false),
284 write_shutdown_(false),
289 TlsMockConnection(TlsServerContext* context,
int accepted_fd)
290 : MockConnection(accepted_fd),
294 pending_write_end_(0),
295 handshake_complete_(false),
296 shutdown_requested_(false),
297 write_shutdown_(false),
302 ~TlsMockConnection()
override {
303 OpenSslErrorQueueGuard error_guard;
304 if (ssl_ !=
nullptr) {
310 bool ok()
const override {
return MockConnection::ok() && ssl_ !=
nullptr; }
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()) {
318 pending_plaintext_.append(
reinterpret_cast<const char*
>(data), size);
326 std::size_t DrainIncoming()
override {
327 if (!ok() || failed_) {
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_);
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) {
344 handshake_complete_ =
true;
345 if (context_ !=
nullptr) {
346 context_->RecordHandshake(ssl_);
350 if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
361 if (pending_write_end_ != 0) {
362 progress += FlushPendingWrites();
363 if (failed_ || pending_write_end_ != 0) {
368 unsigned char request[4096];
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;
378 const int error = SSL_get_error(ssl_, result);
379 if (error == SSL_ERROR_ZERO_RETURN) {
383 shutdown_requested_ =
true;
384 }
else if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE) {
391 progress += FlushPendingWrites();
393 if (!failed_ && shutdown_requested_ && pending_plaintext_.empty() && !write_shutdown_) {
394 const int result = SSL_shutdown(ssl_);
398 (void)::shutdown(server_fd(), SHUT_WR);
399 write_shutdown_ =
true;
402 const int error = SSL_get_error(ssl_, result);
403 if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE) {
412 void ShutdownWrite()
override {
417 if (context_ !=
nullptr && context_->protocol() == TlsApplicationProtocol::kHttp2) {
420 shutdown_requested_ =
true;
421 (void)DrainIncoming();
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) {
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) {
439 SSL_set_accept_state(ssl_);
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) {
452 pending_write_end_ = pending_plaintext_.size();
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);
462 pending_offset_ += written;
464 if (pending_offset_ == pending_write_end_) {
465 pending_write_end_ = 0;
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();
479 if (pending_offset_ == pending_plaintext_.size()) {
480 pending_plaintext_.clear();
482 pending_write_end_ = 0;
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_;
502class TlsCloseNotifyConnection final :
public MockConnection {
504 TlsCloseNotifyConnection() : alert_sent_(false) {}
506 bool WriteAll(
const unsigned char* , std::size_t )
override {
return true; }
508 void ShutdownWrite()
override {
512 static constexpr unsigned char kCloseNotify[] = {
513 0x15, 0x03, 0x03, 0x00, 0x02, 0x01, 0x00,
516 (void)MockConnection::WriteAll(kCloseNotify,
sizeof(kCloseNotify));
517 MockConnection::ShutdownWrite();
528class TlsHelloRequestConnection final :
public MockConnection {
530 TlsHelloRequestConnection() : record_sent_(false) {}
532 bool WriteAll(
const unsigned char* , std::size_t )
override {
return SendOnce(); }
534 void ShutdownWrite()
override {
536 MockConnection::ShutdownWrite();
544 static constexpr unsigned char kHelloRequest[] = {
545 0x16, 0x03, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
547 record_sent_ = MockConnection::WriteAll(kHelloRequest,
sizeof(kHelloRequest));
562 curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain,
564 curl::fuzzer::proto::TlsHandshakeProfile handshake_profile)
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)) {}
572TlsMockTransport::~TlsMockTransport() =
default;
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);
588 if (openssl_close_notify_) {
589 return std::make_unique<TlsCloseNotifyConnection>();
591 if (openssl_hello_request_) {
592 return std::make_unique<TlsHelloRequestConnection>();
594 return std::make_unique<TlsMockConnection>(context_.get());
603 return std::make_unique<TlsMockConnection>(context_.get(), accepted_fd);
608 return context_ ==
nullptr ? 0 : context_->negotiated_tls_version();
613 return context_ ==
nullptr ? 0 : context_->completed_handshake_count();
618 return context_ ==
nullptr ? 0 : context_->reused_session_count();
623 return context_ ==
nullptr ? 0 : context_->write_retry_count();
628 return context_ ==
nullptr ? std::string() : context_->negotiated_alpn();
636 return context_ ==
nullptr ? std::string() : context_->negotiated_group();
644 return context_ ==
nullptr ? std::string() : context_->ech_inner_name();
649 return context_ ==
nullptr ? std::string() : context_->ech_outer_name();
659 curl::fuzzer::proto::TlsHandshakeProfile handshake_profile)
661 handshake_profile) {}
670 curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain,
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) {}
690 transport_.Install(easy);
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;
763 const bool completed_live_handshake = transport_.completed_handshake_count() != 0;
764#if defined(PROTO_FUZZER_OBSERVE_STATE)
768 static bool observed =
false;
769 if (completed_live_handshake && !observed) {
773 const volatile std::size_t counts[] = {
782 if (!completed_live_handshake || exported_session_count_ != 0 ||
783 session_export_attempt_count_ >= kMaxSessionExportAttempts) {
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()) {
792 ++exported_session_count_;
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_;
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.
int negotiated_tls_version() const
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.
bool saw_live_tls_session() const
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.
int negotiated_tls_version() const
std::size_t write_retry_count() const
std::string ech_inner_name() 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 ech_outer_name() 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.