| // Copyright 2025 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "quiche/quic/masque/masque_connection_pool.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/cleanup/cleanup.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/match.h" | |
| #include "absl/strings/numbers.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_split.h" | |
| #include "absl/strings/string_view.h" | |
| #include "openssl/base.h" | |
| #include "openssl/bio.h" | |
| #include "openssl/pool.h" | |
| #include "openssl/ssl.h" | |
| #include "openssl/stack.h" | |
| #include "quiche/quic/core/crypto/proof_verifier.h" | |
| #include "quiche/quic/core/io/quic_default_event_loop.h" | |
| #include "quiche/quic/core/io/quic_event_loop.h" | |
| #include "quiche/quic/core/io/socket.h" | |
| #include "quiche/quic/core/quic_default_clock.h" | |
| #include "quiche/quic/core/quic_time.h" | |
| #include "quiche/quic/core/quic_types.h" | |
| #include "quiche/quic/masque/masque_h2_connection.h" | |
| #include "quiche/quic/platform/api/quic_default_proof_providers.h" | |
| #include "quiche/quic/tools/fake_proof_verifier.h" | |
| #include "quiche/quic/tools/quic_name_lookup.h" | |
| #include "quiche/quic/tools/quic_url.h" | |
| #include "quiche/common/http/http_header_block.h" | |
| #include "quiche/common/platform/api/quiche_logging.h" | |
| #include "quiche/common/quiche_socket_address.h" | |
| #include "quiche/common/quiche_status_utils.h" | |
| #define ENDPOINT info_ << ": " | |
| namespace quic { | |
| namespace { | |
| // Default DNS resolver that uses getaddrinfo(). | |
| class DefaultDnsResolver : public MasqueConnectionPool::DnsResolver { | |
| public: | |
| quiche::QuicheSocketAddress LookupAddress( | |
| int address_family_for_lookup, absl::string_view host, | |
| absl::string_view port) const override { | |
| return tools::LookupAddress(address_family_for_lookup, std::string(host), | |
| std::string(port)); | |
| } | |
| static DefaultDnsResolver* Get() { | |
| static DefaultDnsResolver resolver; | |
| return &resolver; | |
| } | |
| }; | |
| class SimpleFetcher : public MasqueConnectionPool::Visitor { | |
| public: | |
| using Message = MasqueConnectionPool::Message; | |
| using RequestId = MasqueConnectionPool::RequestId; | |
| using DnsConfig = MasqueConnectionPool::DnsConfig; | |
| ~SimpleFetcher() override = default; | |
| static absl::StatusOr<Message> Fetch(const Message& request, | |
| absl::string_view info_string, | |
| const DnsConfig& dns_config, | |
| bool disable_certificate_verification, | |
| SSL_CTX* ssl_ctx) { | |
| SimpleFetcher fetcher; | |
| std::unique_ptr<QuicEventLoop> event_loop = | |
| GetDefaultEventLoop()->Create(QuicDefaultClock::Get()); | |
| bssl::UniquePtr<SSL_CTX> local_ssl_ctx; | |
| if (ssl_ctx == nullptr) { | |
| QUICHE_ASSIGN_OR_RETURN(local_ssl_ctx, | |
| MasqueConnectionPool::CreateSslCtx("", "")); | |
| ssl_ctx = local_ssl_ctx.get(); | |
| } | |
| MasqueConnectionPool pool(event_loop.get(), ssl_ctx, | |
| disable_certificate_verification, dns_config, | |
| &fetcher, info_string); | |
| QUICHE_RETURN_IF_ERROR(pool.SendRequest(request).status()); | |
| while (!fetcher.done_ && fetcher.status_.ok()) { | |
| event_loop->RunEventLoopOnce(quic::QuicTime::Delta::FromMilliseconds(50)); | |
| } | |
| QUICHE_RETURN_IF_ERROR(fetcher.status_); | |
| uint16_t status_code = | |
| MasqueConnectionPool::GetStatusCode(fetcher.response_); | |
| if (status_code < 200 || status_code >= 300) { | |
| return absl::InternalError( | |
| absl::StrCat("Non-2xx status code: ", status_code)); | |
| } | |
| return std::move(fetcher).response_; | |
| } | |
| static absl::StatusOr<Message> Get(absl::string_view url_string, | |
| absl::string_view info_string, | |
| const DnsConfig& dns_config, | |
| bool disable_certificate_verification, | |
| SSL_CTX* ssl_ctx) { | |
| Message request; | |
| QuicUrl url(url_string, "https"); | |
| if (url.host().empty() && !absl::StrContains(url_string, "://")) { | |
| url = QuicUrl(absl::StrCat("https://", url_string)); | |
| } | |
| request.headers[":method"] = "GET"; | |
| request.headers[":scheme"] = url.scheme(); | |
| request.headers[":authority"] = url.HostPort(); | |
| request.headers[":path"] = url.PathParamsQuery(); | |
| return Fetch(std::move(request), info_string, dns_config, | |
| disable_certificate_verification, ssl_ctx); | |
| } | |
| // From MasqueConnectionPool::Visitor. | |
| void OnPoolResponse(MasqueConnectionPool* /*pool*/, RequestId /*request_id*/, | |
| absl::StatusOr<Message>&& response, | |
| bool end_stream) override { | |
| if (!end_stream) { | |
| // This should never happen because we don't stream responses. | |
| status_ = absl::InternalError("Unexpected non-end_stream OnPoolResponse"); | |
| return; | |
| } | |
| if (!response.ok()) { | |
| status_ = response.status(); | |
| return; | |
| } | |
| response_ = std::move(*response); | |
| done_ = true; | |
| } | |
| void OnPoolData(MasqueConnectionPool* /*pool*/, RequestId /*request_id*/, | |
| absl::string_view /*data*/, bool /*end_stream*/) override { | |
| // This should never happen because we don't stream responses. | |
| status_ = absl::InternalError("Unexpected OnPoolData"); | |
| } | |
| private: | |
| SimpleFetcher() = default; | |
| absl::Status status_ = absl::OkStatus(); | |
| Message response_; | |
| bool done_ = false; | |
| }; | |
| } // namespace | |
| absl::StatusOr<MasqueConnectionPool::Message> MasqueSimpleFetch( | |
| const MasqueConnectionPool::Message& request, absl::string_view info_string, | |
| const MasqueConnectionPool::DnsConfig& dns_config, | |
| bool disable_certificate_verification, SSL_CTX* ssl_ctx) { | |
| return SimpleFetcher::Fetch(request, info_string, dns_config, | |
| disable_certificate_verification, ssl_ctx); | |
| } | |
| absl::StatusOr<MasqueConnectionPool::Message> MasqueSimpleGet( | |
| absl::string_view url_string, absl::string_view info_string, | |
| const MasqueConnectionPool::DnsConfig& dns_config, | |
| bool disable_certificate_verification, SSL_CTX* ssl_ctx) { | |
| return SimpleFetcher::Get(url_string, info_string, dns_config, | |
| disable_certificate_verification, ssl_ctx); | |
| } | |
| // static | |
| int16_t MasqueConnectionPool::GetStatusCode(const Message& message) { | |
| auto it = message.headers.find(":status"); | |
| if (it == message.headers.end()) { | |
| return 0; | |
| } | |
| int16_t status_code = 0; | |
| if (!absl::SimpleAtoi(it->second, &status_code)) { | |
| return 0; | |
| } | |
| return status_code; | |
| } | |
| quiche::QuicheSocketAddress MasqueConnectionPool::LookupAddress( | |
| absl::string_view host, absl::string_view port) { | |
| const DnsResolver* dns_resolver = dns_config_.resolver(); | |
| if (dns_resolver == nullptr) { | |
| dns_resolver = DefaultDnsResolver::Get(); | |
| } | |
| dns_config_.ApplyOverrides(&host, &port); | |
| return dns_resolver->LookupAddress(dns_config_.address_family_for_lookup(), | |
| host, port); | |
| } | |
| absl::Status MasqueConnectionPool::DnsConfig::SetAddressFamily( | |
| int address_family) { | |
| if (address_family == 0) { | |
| address_family_for_lookup_ = AF_UNSPEC; | |
| } else if (address_family == 4) { | |
| address_family_for_lookup_ = AF_INET; | |
| } else if (address_family == 6) { | |
| address_family_for_lookup_ = AF_INET6; | |
| } else { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Invalid address_family ", address_family)); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status MasqueConnectionPool::DnsConfig::SetOverrides( | |
| const std::string& overrides) { | |
| if (overrides.empty()) { | |
| return absl::OkStatus(); | |
| } | |
| std::vector<absl::string_view> overrides_split = | |
| absl::StrSplit(overrides, ';'); | |
| for (absl::string_view override : overrides_split) { | |
| std::vector<absl::string_view> override_split = | |
| absl::StrSplit(override, ':'); | |
| if (override_split.size() < 3 || override_split.size() > 4) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Invalid override: \"", override, "\"")); | |
| } | |
| absl::string_view input_host = override_split[0]; | |
| absl::string_view input_port = override_split[1]; | |
| absl::string_view output_host = override_split[2]; | |
| absl::string_view output_port = | |
| override_split.size() > 3 ? override_split[3] : ""; | |
| auto [it, inserted] = overrides_.insert( | |
| {std::make_pair(std::string(input_host), std::string(input_port)), | |
| std::make_pair(std::string(output_host), std::string(output_port))}); | |
| if (!inserted) { | |
| return absl::InvalidArgumentError(absl::StrCat( | |
| "Duplicate override entry: \"", input_host, ":", input_port, "\"")); | |
| } | |
| } | |
| return absl::OkStatus(); | |
| } | |
| void MasqueConnectionPool::DnsConfig::ApplyOverrides( | |
| absl::string_view* host, absl::string_view* port) const { | |
| for (const auto& [input, output] : overrides_) { | |
| if ((input.first == *host || input.first.empty()) && | |
| (input.second == *port || input.second.empty())) { | |
| *host = output.first; | |
| if (!output.second.empty()) { | |
| *port = output.second; | |
| } | |
| return; | |
| } | |
| } | |
| } | |
| MasqueConnectionPool::MasqueConnectionPool( | |
| QuicEventLoop* event_loop, SSL_CTX* ssl_ctx, | |
| bool disable_certificate_verification, const DnsConfig& dns_config, | |
| Visitor* visitor, absl::string_view info_string) | |
| : event_loop_(event_loop), | |
| tls_ssl_ctx_(ssl_ctx), | |
| disable_certificate_verification_(disable_certificate_verification), | |
| dns_config_(dns_config), | |
| visitor_(visitor), | |
| info_(info_string) {} | |
| void MasqueConnectionPool::OnConnectionReady(MasqueH2Connection* connection) { | |
| SendPendingRequests(connection); | |
| } | |
| void MasqueConnectionPool::OnConnectionFinished(MasqueH2Connection* connection, | |
| absl::Status error) { | |
| FailPendingRequests( | |
| connection, | |
| error.ok() ? absl::InternalError( | |
| "Connection finished before receiving complete response") | |
| : error); | |
| } | |
| void MasqueConnectionPool::OnRequest(MasqueH2Connection* /*connection*/, | |
| int32_t /*stream_id*/, | |
| const quiche::HttpHeaderBlock& /*headers*/, | |
| const std::string& /*body*/) { | |
| QUICHE_LOG(FATAL) << ENDPOINT << "Client cannot receive requests"; | |
| } | |
| void MasqueConnectionPool::OnResponse(MasqueH2Connection* connection, | |
| int32_t stream_id, | |
| const quiche::HttpHeaderBlock& headers, | |
| const std::string& body, | |
| bool end_stream) { | |
| bool found = false; | |
| for (auto it = pending_requests_.begin(); it != pending_requests_.end();) { | |
| RequestId request_id = it->first; | |
| PendingRequest& pending_request = *it->second; | |
| if (pending_request.connection == connection && | |
| pending_request.stream_id == stream_id) { | |
| Message response; | |
| response.headers = headers.Clone(); | |
| response.body = body; | |
| response.draining = connection->draining(); | |
| if (end_stream) { | |
| pending_request.response_done = true; | |
| } | |
| if (end_stream && pending_request.end_stream_pending) { | |
| pending_requests_.erase(it); | |
| } | |
| visitor_->OnPoolResponse(this, request_id, std::move(response), | |
| end_stream); | |
| found = true; | |
| break; | |
| } | |
| ++it; | |
| } | |
| if (!found) { | |
| QUICHE_LOG(ERROR) << ENDPOINT | |
| << "Received unexpected response for unknown request: " | |
| << headers.DebugString(); | |
| } | |
| } | |
| void MasqueConnectionPool::OnDataForStream(MasqueH2Connection* connection, | |
| int32_t stream_id, | |
| absl::string_view data, | |
| bool end_stream) { | |
| for (auto it = pending_requests_.begin(); it != pending_requests_.end(); | |
| ++it) { | |
| RequestId request_id = it->first; | |
| PendingRequest& pending_request = *it->second; | |
| if (pending_request.connection == connection && | |
| pending_request.stream_id == stream_id) { | |
| if (end_stream) { | |
| pending_request.response_done = true; | |
| } | |
| if (end_stream && pending_request.end_stream_pending) { | |
| pending_requests_.erase(it); | |
| } | |
| visitor_->OnPoolData(this, request_id, data, end_stream); | |
| break; | |
| } | |
| } | |
| } | |
| void MasqueConnectionPool::OnStreamFailure(MasqueH2Connection* connection, | |
| int32_t stream_id, | |
| absl::Status error) { | |
| for (auto it = pending_requests_.begin(); it != pending_requests_.end();) { | |
| RequestId request_id = it->first; | |
| PendingRequest& pending_request = *it->second; | |
| if (pending_request.connection == connection && | |
| pending_request.stream_id == stream_id) { | |
| pending_requests_.erase(it++); | |
| visitor_->OnPoolResponse(this, request_id, error, /*end_stream=*/true); | |
| break; | |
| } | |
| ++it; | |
| } | |
| } | |
| absl::StatusOr<MasqueConnectionPool::RequestId> | |
| MasqueConnectionPool::SendRequest(const Message& request, bool mtls, | |
| bool end_stream, bool stream_response) { | |
| auto authority = request.headers.find(":authority"); | |
| if (authority == request.headers.end()) { | |
| return absl::InvalidArgumentError("Request missing :authority header"); | |
| } | |
| QUICHE_ASSIGN_OR_RETURN( | |
| ConnectionState * connection, | |
| GetOrCreateConnectionState(std::string(authority->second), mtls)); | |
| auto pending_request = std::make_unique<PendingRequest>(); | |
| if (connection->connection() != nullptr && | |
| !connection->connection()->aborted()) { | |
| QUICHE_LOG(INFO) << ENDPOINT << "Reusing existing connection " | |
| << connection->connection()->info() << " to " | |
| << authority->second; | |
| pending_request->connection = connection->connection(); | |
| pending_request->stream_id = connection->connection()->SendRequest( | |
| request.headers, request.body, end_stream, stream_response); | |
| if (pending_request->stream_id < 0) { | |
| return absl::InternalError( | |
| absl::StrCat("Failed to send request to ", authority->second)); | |
| } | |
| connection->connection()->AttemptToSend(); | |
| } else { | |
| QUICHE_LOG(INFO) << ENDPOINT << "No existing connection to " | |
| << authority->second; | |
| } | |
| RequestId request_id = ++next_request_id_; | |
| pending_request->request.headers = request.headers.Clone(); | |
| pending_request->request.body = request.body; | |
| pending_request->stream_response = stream_response; | |
| pending_request->end_stream_pending = end_stream; | |
| pending_requests_.insert({request_id, std::move(pending_request)}); | |
| return request_id; | |
| } | |
| absl::Status MasqueConnectionPool::SendBodyChunk(RequestId request_id, | |
| const std::string& body, | |
| bool end_stream) { | |
| auto it = pending_requests_.find(request_id); | |
| if (it == pending_requests_.end()) { | |
| return absl::InternalError( | |
| absl::StrCat("SendBodyChunk called for unknown request ", request_id)); | |
| } | |
| PendingRequest& pending_request = *it->second; | |
| if (pending_request.connection == nullptr || pending_request.stream_id < 0) { | |
| // Connection not ready yet, append to pending body chunks. | |
| pending_request.pending_data += body; | |
| pending_request.end_stream_pending = end_stream; | |
| return absl::OkStatus(); | |
| } | |
| if (pending_request.end_stream_pending) { | |
| return absl::FailedPreconditionError(absl::StrCat( | |
| "SendBodyChunk called when end_stream already pending ", request_id)); | |
| } | |
| pending_request.end_stream_pending = end_stream; | |
| pending_request.connection->SendBodyChunk(pending_request.stream_id, body, | |
| end_stream); | |
| pending_request.connection->AttemptToSend(); | |
| if (end_stream && pending_request.response_done) { | |
| pending_requests_.erase(it); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::StatusOr<MasqueConnectionPool::ConnectionState*> | |
| MasqueConnectionPool::GetOrCreateConnectionState(const std::string& authority, | |
| bool mtls) { | |
| std::string entry = absl::StrCat((mtls ? "m" : ""), "tls:", authority); | |
| auto connection_state_it = connections_.find(entry); | |
| if (connection_state_it != connections_.end()) { | |
| return connection_state_it->second.get(); | |
| } | |
| auto connection_state = std::make_unique<ConnectionState>(this); | |
| connection_state->set_mtls(mtls); | |
| QUICHE_RETURN_IF_ERROR(connection_state->SetupSocket( | |
| authority, disable_certificate_verification_)); | |
| return connections_.insert({entry, std::move(connection_state)}) | |
| .first->second.get(); | |
| } | |
| void MasqueConnectionPool::AttachConnectionToPendingRequests( | |
| const std::string& authority, MasqueH2Connection* connection) { | |
| for (auto it = pending_requests_.begin(); it != pending_requests_.end(); | |
| ++it) { | |
| PendingRequest& pending_request = *it->second; | |
| auto authority_header = pending_request.request.headers.find(":authority"); | |
| if (authority_header == pending_request.request.headers.end()) { | |
| QUICHE_LOG(ERROR) << ENDPOINT << "Request missing :authority header"; | |
| continue; | |
| } | |
| if (authority_header->second != authority) { | |
| continue; | |
| } | |
| QUICHE_LOG(INFO) << ENDPOINT << "Attaching connection " | |
| << connection->info() << " to pending request for " | |
| << authority; | |
| pending_request.connection = connection; | |
| } | |
| } | |
| void MasqueConnectionPool::SendPendingRequests(MasqueH2Connection* connection) { | |
| for (auto it = pending_requests_.begin(); it != pending_requests_.end();) { | |
| RequestId request_id = it->first; | |
| PendingRequest& pending_request = *it->second; | |
| if (pending_request.connection != connection) { | |
| ++it; | |
| continue; | |
| } | |
| bool is_request_complete = pending_request.pending_data.empty() && | |
| pending_request.end_stream_pending; | |
| QUICHE_LOG(INFO) << ENDPOINT << "Sending pending request ID " << request_id; | |
| int32_t stream_id = connection->SendRequest( | |
| pending_request.request.headers, pending_request.request.body, | |
| is_request_complete, pending_request.stream_response); | |
| if (stream_id < 0) { | |
| QUICHE_LOG(ERROR) << ENDPOINT << "Failed to send request ID " | |
| << request_id << " on connection " | |
| << connection->info(); | |
| visitor_->OnPoolResponse(this, request_id, | |
| absl::InternalError("Failed to send request"), | |
| /*end_stream=*/true); | |
| pending_requests_.erase(it++); | |
| continue; | |
| } | |
| if (!pending_request.pending_data.empty()) { | |
| connection->AttemptToSend(); | |
| } | |
| if (!is_request_complete) { | |
| connection->SendBodyChunk(stream_id, pending_request.pending_data, | |
| pending_request.end_stream_pending); | |
| } | |
| pending_request.pending_data.clear(); | |
| pending_request.stream_id = stream_id; | |
| ++it; | |
| } | |
| } | |
| void MasqueConnectionPool::FailPendingRequests(MasqueH2Connection* connection, | |
| const absl::Status& error) { | |
| for (auto it = pending_requests_.begin(); it != pending_requests_.end();) { | |
| RequestId request_id = it->first; | |
| PendingRequest& pending_request = *it->second; | |
| if (pending_request.connection != connection) { | |
| ++it; | |
| continue; | |
| } | |
| visitor_->OnPoolResponse(this, request_id, error, /*end_stream=*/true); | |
| pending_requests_.erase(it++); | |
| } | |
| } | |
| MasqueConnectionPool::ConnectionState::ConnectionState( | |
| MasqueConnectionPool* connection_pool) | |
| : connection_pool_(connection_pool), info_(connection_pool->info()) {} | |
| MasqueConnectionPool::ConnectionState::~ConnectionState() { | |
| if (socket_ != kInvalidSocketFd) { | |
| QUICHE_LOG(INFO) << ENDPOINT << "Closing socket fd " << socket_ << " for " | |
| << authority_; | |
| if (!connection_pool_->event_loop()->UnregisterSocket(socket_)) { | |
| QUICHE_LOG(ERROR) << ENDPOINT << "Failed to unregister socket"; | |
| } | |
| if (!socket_api::Close(socket_).ok()) { | |
| QUICHE_LOG(ERROR) << ENDPOINT << "Error while closing socket"; | |
| } | |
| socket_ = kInvalidSocketFd; | |
| } | |
| } | |
| absl::Status MasqueConnectionPool::ConnectionState::SetupSocket( | |
| const std::string& authority, bool disable_certificate_verification) { | |
| authority_ = authority; | |
| std::vector<std::string> authority_split = | |
| absl::StrSplit(authority_, absl::MaxSplits(':', 1)); | |
| std::string port; | |
| if (authority_split.size() == 2) { | |
| host_ = authority_split[0]; | |
| port = authority_split[1]; | |
| } else { | |
| host_ = authority_split[0]; | |
| port = "443"; | |
| } | |
| quiche::QuicheSocketAddress socket_address = | |
| connection_pool_->LookupAddress(host_, port); | |
| if (!socket_address.IsInitialized()) { | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Failed to resolve address for \"", authority_, "\"")); | |
| } | |
| absl::StatusOr<SocketFd> create_result = socket_api::CreateSocket( | |
| socket_address.host().address_family(), socket_api::SocketProtocol::kTcp, | |
| /*blocking=*/false); | |
| if (!create_result.ok() || create_result.value() == kInvalidSocketFd) { | |
| return absl::InternalError(absl::StrCat("Failed to create socket: ", | |
| create_result.status().message())); | |
| } | |
| socket_ = create_result.value(); | |
| QUICHE_LOG(INFO) << ENDPOINT << "Socket fd " << socket_ | |
| << " starting connect to " << socket_address << " for " | |
| << authority_; | |
| // Ignore result because asynchronous connect is expected to fail. | |
| (void)socket_api::Connect(socket_, socket_address); | |
| if (!connection_pool_->event_loop()->RegisterSocket( | |
| socket_, kSocketEventReadable | kSocketEventWritable, this)) { | |
| return absl::InternalError("Failed to register socket with the event loop"); | |
| } | |
| if (disable_certificate_verification) { | |
| proof_verifier_ = std::make_unique<FakeProofVerifier>(); | |
| } else { | |
| proof_verifier_ = CreateDefaultProofVerifier(host_); | |
| if (!proof_verifier_) { | |
| QUICHE_LOG(FATAL) << ENDPOINT | |
| << "The default proof verifier is not supported. Pass " | |
| "in --disable_certificate_verification."; | |
| } | |
| } | |
| return absl::OkStatus(); | |
| } | |
| void MasqueConnectionPool::ConnectionState::OnSocketEvent( | |
| QuicEventLoop* event_loop, SocketFd fd, QuicSocketEventMask events) { | |
| auto cleanup = absl::MakeCleanup([this, event_loop, fd]() { | |
| if (!event_loop->SupportsEdgeTriggered() && | |
| (!connection_ || !connection_->aborted())) { | |
| if (!event_loop->RearmSocket( | |
| fd, kSocketEventReadable | kSocketEventWritable)) { | |
| QUICHE_LOG(FATAL) << ENDPOINT << "Failed to re-arm socket " << fd; | |
| } | |
| } | |
| }); | |
| if (fd != socket_) { | |
| return; | |
| } | |
| if (connection_ && ((events & kSocketEventReadable) != 0)) { | |
| connection_->OnTransportReadable(); | |
| } | |
| if ((events & kSocketEventWritable) != 0) { | |
| if (!ssl_) { | |
| ssl_.reset((SSL_new(connection_pool_->GetSslCtx(mtls_)))); | |
| SSL_set_connect_state(ssl_.get()); | |
| if (SSL_set_app_data(ssl_.get(), this) != 1) { | |
| QUICHE_LOG(FATAL) << ENDPOINT << "SSL_set_app_data failed"; | |
| } | |
| SSL_set_custom_verify(ssl_.get(), SSL_VERIFY_PEER, &VerifyCallback); | |
| if (SSL_set_tlsext_host_name(ssl_.get(), host_.c_str()) != 1) { | |
| QUICHE_LOG(FATAL) << ENDPOINT << "SSL_set_tlsext_host_name failed"; | |
| } | |
| static constexpr uint8_t kAlpnProtocols[] = { | |
| // clang-format off | |
| 0x02, 'h', '2', // h2 | |
| // clang-format on | |
| }; | |
| if (SSL_set_alpn_protos(ssl_.get(), kAlpnProtocols, | |
| sizeof(kAlpnProtocols)) != 0) { | |
| QUICHE_LOG(FATAL) << ENDPOINT << "SSL_set_alpn_protos failed"; | |
| } | |
| BIO* bio = BIO_new_socket(socket_, BIO_CLOSE); | |
| SSL_set_bio(ssl_.get(), bio, bio); | |
| // `SSL_set_bio` causes `ssl_` to take ownership of `bio`. | |
| connection_ = std::make_unique<MasqueH2Connection>( | |
| ssl_.get(), /*is_server=*/false, connection_pool_, | |
| connection_pool_->info()); | |
| info_ = connection_->info(); | |
| connection_pool_->AttachConnectionToPendingRequests(authority_, | |
| connection_.get()); | |
| connection_->OnTransportReadable(); | |
| } | |
| connection_->AttemptToSend(); | |
| } | |
| } | |
| // static | |
| enum ssl_verify_result_t MasqueConnectionPool::ConnectionState::VerifyCallback( | |
| SSL* ssl, uint8_t* out_alert) { | |
| return static_cast<MasqueConnectionPool::ConnectionState*>( | |
| SSL_get_app_data(ssl)) | |
| ->VerifyCertificate(ssl, out_alert); | |
| } | |
| enum ssl_verify_result_t | |
| MasqueConnectionPool::ConnectionState::VerifyCertificate(SSL* ssl, | |
| uint8_t* out_alert) { | |
| const STACK_OF(CRYPTO_BUFFER)* cert_chain = SSL_get0_peer_certificates(ssl); | |
| if (cert_chain == nullptr) { | |
| QUICHE_LOG(ERROR) << ENDPOINT << "No certificate chain"; | |
| *out_alert = SSL_AD_INTERNAL_ERROR; | |
| return ssl_verify_invalid; | |
| } | |
| std::vector<absl::string_view> certs; | |
| for (CRYPTO_BUFFER* cert : cert_chain) { | |
| certs.push_back(absl::string_view( | |
| reinterpret_cast<const char*>(CRYPTO_BUFFER_data(cert)), | |
| CRYPTO_BUFFER_len(cert))); | |
| } | |
| const uint8_t* ocsp_response_raw; | |
| size_t ocsp_response_len; | |
| SSL_get0_ocsp_response(ssl, &ocsp_response_raw, &ocsp_response_len); | |
| std::string ocsp_response(reinterpret_cast<const char*>(ocsp_response_raw), | |
| ocsp_response_len); | |
| const uint8_t* sct_list_raw; | |
| size_t sct_list_len; | |
| SSL_get0_signed_cert_timestamp_list(ssl, &sct_list_raw, &sct_list_len); | |
| std::string cert_sct(reinterpret_cast<const char*>(sct_list_raw), | |
| sct_list_len); | |
| std::string error_details; | |
| std::unique_ptr<ProofVerifyDetails> details; | |
| QuicAsyncStatus verify_status = proof_verifier_->VerifyCertChain( | |
| host_, /*port=*/443, certs, ocsp_response, cert_sct, | |
| /*context=*/nullptr, &error_details, &details, out_alert, | |
| /*callback=*/nullptr); | |
| if (verify_status != QUIC_SUCCESS) { | |
| // TODO(dschinazi) properly handle QUIC_PENDING. | |
| QUICHE_LOG(ERROR) << ENDPOINT << "Failed to verify certificate" | |
| << (verify_status == QUIC_PENDING ? " (pending)" : "") | |
| << ": " << error_details; | |
| return ssl_verify_invalid; | |
| } | |
| QUICHE_LOG(INFO) << ENDPOINT << "Successfully verified certificate"; | |
| return ssl_verify_ok; | |
| } | |
| // static | |
| absl::StatusOr<bssl::UniquePtr<SSL_CTX>> MasqueConnectionPool::CreateSslCtx( | |
| const std::string& client_cert_file, | |
| const std::string& client_cert_key_file) { | |
| if (client_cert_file.empty() != client_cert_key_file.empty()) { | |
| return absl::InvalidArgumentError( | |
| "Both private key and certificate chain are required when using client " | |
| "certificates"); | |
| } | |
| bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method())); | |
| if (!client_cert_key_file.empty() && | |
| !SSL_CTX_use_PrivateKey_file(ctx.get(), client_cert_key_file.c_str(), | |
| SSL_FILETYPE_PEM)) { | |
| QUICHE_LOG(ERROR) << "Failed to load client certificate private key: " | |
| << client_cert_key_file; | |
| return absl::InternalError( | |
| absl::StrCat("Failed to load client certificate private key: ", | |
| client_cert_key_file)); | |
| } | |
| if (!client_cert_file.empty() && !SSL_CTX_use_certificate_chain_file( | |
| ctx.get(), client_cert_file.c_str())) { | |
| QUICHE_LOG(ERROR) << "Failed to load client certificate chain: " | |
| << client_cert_file; | |
| return absl::InternalError(absl::StrCat( | |
| "Failed to load client certificate chain: ", client_cert_file)); | |
| } | |
| SSL_CTX_set_min_proto_version(ctx.get(), TLS1_2_VERSION); | |
| SSL_CTX_set_max_proto_version(ctx.get(), TLS1_3_VERSION); | |
| SSL_CTX_set_mode(ctx.get(), SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); | |
| return ctx; | |
| } | |
| // static | |
| absl::StatusOr<bssl::UniquePtr<SSL_CTX>> | |
| MasqueConnectionPool::CreateSslCtxFromData( | |
| const std::string& client_cert_pem_data, | |
| const std::string& client_cert_key_data) { | |
| bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method())); | |
| // Load public cert. | |
| BIO* cert_bio = BIO_new_mem_buf(client_cert_pem_data.c_str(), -1); | |
| QUICHE_CHECK(cert_bio); | |
| X509* cert = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr); | |
| QUICHE_CHECK(cert); | |
| BIO_free(cert_bio); | |
| int rv = SSL_CTX_use_certificate(ctx.get(), cert); | |
| QUICHE_CHECK_EQ(rv, 1); | |
| X509_free(cert); | |
| // Load private key. | |
| BIO* key_bio = BIO_new_mem_buf(client_cert_key_data.c_str(), -1); | |
| QUICHE_CHECK(key_bio); | |
| EVP_PKEY* private_key = | |
| PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr, nullptr); | |
| QUICHE_CHECK(private_key); | |
| BIO_free(key_bio); | |
| rv = SSL_CTX_use_PrivateKey(ctx.get(), private_key); | |
| QUICHE_CHECK_EQ(rv, 1); | |
| EVP_PKEY_free(private_key); | |
| SSL_CTX_set_min_proto_version(ctx.get(), TLS1_2_VERSION); | |
| SSL_CTX_set_max_proto_version(ctx.get(), TLS1_3_VERSION); | |
| SSL_CTX_set_mode(ctx.get(), SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); | |
| return ctx; | |
| } | |
| } // namespace quic | |
| #undef ENDPOINT |