[go: nahoru, domu]

blob: 2ab34acfdf00ce46249f715a541f50e19cfd6514 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/socket/connect_job_factory.h"
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/containers/flat_set.h"
#include "base/memory/scoped_refptr.h"
#include "net/base/host_port_pair.h"
#include "net/base/network_anonymization_key.h"
#include "net/base/privacy_mode.h"
#include "net/base/proxy_chain.h"
#include "net/base/proxy_server.h"
#include "net/base/request_priority.h"
#include "net/dns/public/secure_dns_policy.h"
#include "net/http/http_proxy_connect_job.h"
#include "net/socket/connect_job.h"
#include "net/socket/next_proto.h"
#include "net/socket/socket_tag.h"
#include "net/socket/socks_connect_job.h"
#include "net/socket/ssl_connect_job.h"
#include "net/socket/transport_connect_job.h"
#include "net/ssl/ssl_config.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "url/gurl.h"
#include "url/scheme_host_port.h"
namespace net {
namespace {
template <typename T>
std::unique_ptr<T> CreateFactoryIfNull(std::unique_ptr<T> in) {
if (in)
return in;
return std::make_unique<T>();
}
bool UsingSsl(const ConnectJobFactory::Endpoint& endpoint) {
if (absl::holds_alternative<url::SchemeHostPort>(endpoint)) {
return GURL::SchemeIsCryptographic(
base::ToLowerASCII(absl::get<url::SchemeHostPort>(endpoint).scheme()));
}
DCHECK(
absl::holds_alternative<ConnectJobFactory::SchemelessEndpoint>(endpoint));
return absl::get<ConnectJobFactory::SchemelessEndpoint>(endpoint).using_ssl;
}
HostPortPair ToHostPortPair(const ConnectJobFactory::Endpoint& endpoint) {
if (absl::holds_alternative<url::SchemeHostPort>(endpoint)) {
return HostPortPair::FromSchemeHostPort(
absl::get<url::SchemeHostPort>(endpoint));
}
DCHECK(
absl::holds_alternative<ConnectJobFactory::SchemelessEndpoint>(endpoint));
return absl::get<ConnectJobFactory::SchemelessEndpoint>(endpoint)
.host_port_pair;
}
TransportSocketParams::Endpoint ToTransportEndpoint(
const ConnectJobFactory::Endpoint& endpoint) {
if (absl::holds_alternative<url::SchemeHostPort>(endpoint))
return absl::get<url::SchemeHostPort>(endpoint);
DCHECK(
absl::holds_alternative<ConnectJobFactory::SchemelessEndpoint>(endpoint));
return absl::get<ConnectJobFactory::SchemelessEndpoint>(endpoint)
.host_port_pair;
}
base::flat_set<std::string> SupportedProtocolsFromSSLConfig(
const SSLConfig& config) {
// We convert because `SSLConfig` uses `NextProto` for ALPN protocols while
// `TransportConnectJob` and DNS logic needs `std::string`. See
// https://crbug.com/1286835.
return base::MakeFlatSet<std::string>(config.alpn_protos, /*comp=*/{},
NextProtoToString);
}
// Populates `ssl_config's` ALPN-related fields. Namely, `alpn_protos`,
// `application_settings`, `renego_allowed_default`, and
// `renego_allowed_for_protos`.
//
// In the case of AlpnMode::kDisabled, clears all of the fields.
//
// In the case of AlpnMode::kHttp11Only sets `alpn_protos` to only allow
// HTTP/1.1 negotiation.
//
// In the case of AlpnMode::kHttpAll, copying `alpn_protos` from
// `common_connect_job_params`, and gives HttpServerProperties a chance to force
// use of HTTP/1.1 only.
//
// If `alpn_mode` is not AlpnMode::kDisabled, then `server` must be a
// SchemeHostPort, as it makes no sense to negotiate ALPN when the scheme isn't
// known.
void ConfigureAlpn(
const ConnectJobFactory::Endpoint& endpoint,
ConnectJobFactory::AlpnMode alpn_mode,
const net::NetworkAnonymizationKey& network_anonymization_key,
const CommonConnectJobParams& common_connect_job_params,
SSLConfig& ssl_config,
bool renego_allowed) {
if (alpn_mode == ConnectJobFactory::AlpnMode::kDisabled) {
ssl_config.alpn_protos = {};
ssl_config.application_settings = {};
ssl_config.renego_allowed_default = false;
return;
}
DCHECK(absl::holds_alternative<url::SchemeHostPort>(endpoint));
if (alpn_mode == ConnectJobFactory::AlpnMode::kHttp11Only) {
ssl_config.alpn_protos = {kProtoHTTP11};
ssl_config.application_settings =
*common_connect_job_params.application_settings;
} else {
DCHECK_EQ(alpn_mode, ConnectJobFactory::AlpnMode::kHttpAll);
DCHECK(absl::holds_alternative<url::SchemeHostPort>(endpoint));
ssl_config.alpn_protos = *common_connect_job_params.alpn_protos;
ssl_config.application_settings =
*common_connect_job_params.application_settings;
if (common_connect_job_params.http_server_properties) {
common_connect_job_params.http_server_properties->MaybeForceHTTP11(
absl::get<url::SchemeHostPort>(endpoint), network_anonymization_key,
&ssl_config);
}
}
// Prior to HTTP/2 and SPDY, some servers used TLS renegotiation to request
// TLS client authentication after the HTTP request was sent. Allow
// renegotiation for only those connections.
//
// Note that this does NOT implement the provision in
// https://http2.github.io/http2-spec/#rfc.section.9.2.1 which allows the
// server to request a renegotiation immediately before sending the
// connection preface as waiting for the preface would cost the round trip
// that False Start otherwise saves.
ssl_config.renego_allowed_default = renego_allowed;
if (renego_allowed) {
ssl_config.renego_allowed_for_protos = {kProtoHTTP11};
}
}
} // namespace
ConnectJobFactory::ConnectJobFactory(
std::unique_ptr<HttpProxyConnectJob::Factory>
http_proxy_connect_job_factory,
std::unique_ptr<SOCKSConnectJob::Factory> socks_connect_job_factory,
std::unique_ptr<SSLConnectJob::Factory> ssl_connect_job_factory,
std::unique_ptr<TransportConnectJob::Factory> transport_connect_job_factory)
: http_proxy_connect_job_factory_(
CreateFactoryIfNull(std::move(http_proxy_connect_job_factory))),
socks_connect_job_factory_(
CreateFactoryIfNull(std::move(socks_connect_job_factory))),
ssl_connect_job_factory_(
CreateFactoryIfNull(std::move(ssl_connect_job_factory))),
transport_connect_job_factory_(
CreateFactoryIfNull(std::move(transport_connect_job_factory))) {}
ConnectJobFactory::~ConnectJobFactory() = default;
std::unique_ptr<ConnectJob> ConnectJobFactory::CreateConnectJob(
url::SchemeHostPort endpoint,
const ProxyChain& proxy_chain,
const std::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
const std::vector<SSLConfig::CertAndStatus>& allowed_bad_certs,
ConnectJobFactory::AlpnMode alpn_mode,
bool force_tunnel,
PrivacyMode privacy_mode,
const OnHostResolutionCallback& resolution_callback,
RequestPriority request_priority,
SocketTag socket_tag,
const NetworkAnonymizationKey& network_anonymization_key,
SecureDnsPolicy secure_dns_policy,
bool disable_cert_network_fetches,
const CommonConnectJobParams* common_connect_job_params,
ConnectJob::Delegate* delegate) const {
return CreateConnectJob(
Endpoint(std::move(endpoint)), proxy_chain, proxy_annotation_tag,
allowed_bad_certs, alpn_mode, force_tunnel, privacy_mode,
resolution_callback, request_priority, socket_tag,
network_anonymization_key, secure_dns_policy,
disable_cert_network_fetches, common_connect_job_params, delegate);
}
std::unique_ptr<ConnectJob> ConnectJobFactory::CreateConnectJob(
bool using_ssl,
HostPortPair endpoint,
const ProxyChain& proxy_chain,
const std::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
bool force_tunnel,
PrivacyMode privacy_mode,
const OnHostResolutionCallback& resolution_callback,
RequestPriority request_priority,
SocketTag socket_tag,
const NetworkAnonymizationKey& network_anonymization_key,
SecureDnsPolicy secure_dns_policy,
const CommonConnectJobParams* common_connect_job_params,
ConnectJob::Delegate* delegate) const {
SchemelessEndpoint schemeless_endpoint{using_ssl, std::move(endpoint)};
return CreateConnectJob(
std::move(schemeless_endpoint), proxy_chain, proxy_annotation_tag,
/*allowed_bad_certs=*/{}, ConnectJobFactory::AlpnMode::kDisabled,
force_tunnel, privacy_mode, resolution_callback, request_priority,
socket_tag, network_anonymization_key, secure_dns_policy,
/*disable_cert_network_fetches=*/false, common_connect_job_params,
delegate);
}
std::unique_ptr<ConnectJob> ConnectJobFactory::CreateConnectJob(
Endpoint endpoint,
const ProxyChain& proxy_chain,
const std::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
const std::vector<SSLConfig::CertAndStatus>& allowed_bad_certs,
ConnectJobFactory::AlpnMode alpn_mode,
bool force_tunnel,
PrivacyMode privacy_mode,
const OnHostResolutionCallback& resolution_callback,
RequestPriority request_priority,
SocketTag socket_tag,
const NetworkAnonymizationKey& network_anonymization_key,
SecureDnsPolicy secure_dns_policy,
bool disable_cert_network_fetches,
const CommonConnectJobParams* common_connect_job_params,
ConnectJob::Delegate* delegate) const {
scoped_refptr<HttpProxySocketParams> http_proxy_params;
scoped_refptr<SOCKSSocketParams> socks_params;
base::flat_set<std::string> no_alpn_protocols;
DCHECK(proxy_chain.IsValid());
if (!proxy_chain.is_direct()) {
// The first iteration of this loop is taken for all types of proxies and
// creates a TransportSocketParams and other socket params based on the
// proxy type. For nested proxies, we then create additional SSLSocketParam
// and HttpProxySocketParam objects for the remaining hops. This is done by
// working backwards through the proxy chain and creating socket params
// such that connect jobs will be created recursively with dependencies in
// the correct order (in other words, the inner-most connect job will
// establish a connection to the first proxy, and then that connection
// will get used to establish a connection to the second proxy).
for (size_t proxy_index = 0; proxy_index < proxy_chain.length();
++proxy_index) {
const ProxyServer& proxy_server = proxy_chain.GetProxyServer(proxy_index);
SSLConfig proxy_server_ssl_config;
if (proxy_server.is_secure_http_like()) {
// Disable cert verification network fetches for secure proxies, since
// those network requests are probably going to need to go through the
// proxy chain too.
//
// Any proxy-specific SSL behavior here should also be configured for
// QUIC proxies.
//
proxy_server_ssl_config.disable_cert_verification_network_fetches =
true;
ConfigureAlpn(url::SchemeHostPort(url::kHttpsScheme,
proxy_server.host_port_pair().host(),
proxy_server.host_port_pair().port()),
// Always enable ALPN for proxies.
ConnectJobFactory::AlpnMode::kHttpAll,
network_anonymization_key, *common_connect_job_params,
proxy_server_ssl_config, /*renego_allowed=*/false);
}
scoped_refptr<TransportSocketParams> proxy_tcp_params;
if (proxy_index == 0) {
// In the first iteration create the only TransportSocketParams object,
// corresponding to the transport socket we want to create to the first
// proxy.
// TODO(crbug.com/1206799): For an http-like proxy, should this pass a
// `SchemeHostPort`, so proxies can participate in ECH? Note doing so
// with `SCHEME_HTTP` requires handling the HTTPS record upgrade.
proxy_tcp_params = base::MakeRefCounted<TransportSocketParams>(
proxy_server.host_port_pair(), proxy_dns_network_anonymization_key_,
secure_dns_policy, resolution_callback,
proxy_server.is_secure_http_like()
? SupportedProtocolsFromSSLConfig(proxy_server_ssl_config)
: no_alpn_protocols);
} else {
// TODO(https://crbug.com/1491092): For now we will assume that proxy
// chains with multiple proxies must all use HTTPS.
CHECK(http_proxy_params);
CHECK(http_proxy_params->ssl_params());
CHECK(
proxy_chain.GetProxyServer(proxy_index - 1).is_secure_http_like());
}
if (proxy_server.is_http_like()) {
scoped_refptr<SSLSocketParams> ssl_params;
if (proxy_server.is_secure_http_like()) {
// Set `ssl_params`, and unset `proxy_tcp_params`.
ssl_params = base::MakeRefCounted<SSLSocketParams>(
std::move(proxy_tcp_params), /*socks_proxy_params=*/nullptr,
std::move(http_proxy_params), proxy_server.host_port_pair(),
proxy_server_ssl_config, PRIVACY_MODE_DISABLED,
network_anonymization_key);
proxy_tcp_params = nullptr;
}
// The endpoint parameter for this HttpProxySocketParams, which is what
// we will CONNECT to, should correspond to either `endpoint` (for
// one-hop proxies) or the proxy server at index 1 (for n-hop proxies).
HostPortPair connect_host_port_pair;
bool should_tunnel;
if (proxy_index + 1 == proxy_chain.length()) {
connect_host_port_pair = ToHostPortPair(endpoint);
should_tunnel = force_tunnel || UsingSsl(endpoint) ||
!proxy_chain.is_get_to_proxy_allowed();
} else {
const auto& next_proxy_server =
proxy_chain.GetProxyServer(proxy_index + 1);
connect_host_port_pair = next_proxy_server.host_port_pair();
// TODO(https://crbug.com/1491092): For now we will assume that proxy
// chains with multiple proxies must all use HTTPS.
CHECK(next_proxy_server.is_secure_http_like());
should_tunnel = true;
}
// TODO(crbug.com/1206799): Pass `endpoint` directly (preserving
// scheme when available)?
http_proxy_params = base::MakeRefCounted<HttpProxySocketParams>(
std::move(proxy_tcp_params), std::move(ssl_params),
connect_host_port_pair, proxy_chain, proxy_index, should_tunnel,
*proxy_annotation_tag, network_anonymization_key,
secure_dns_policy);
} else {
DCHECK(proxy_server.is_socks());
DCHECK_EQ(1u, proxy_chain.length());
// TODO(crbug.com/1206799): Pass `endpoint` directly (preserving scheme
// when available)?
socks_params = base::MakeRefCounted<SOCKSSocketParams>(
std::move(proxy_tcp_params),
proxy_server.scheme() == ProxyServer::SCHEME_SOCKS5,
ToHostPortPair(endpoint), network_anonymization_key,
*proxy_annotation_tag);
}
}
}
// Deal with SSL - which layers on top of any given proxy.
if (UsingSsl(endpoint)) {
scoped_refptr<TransportSocketParams> ssl_tcp_params;
SSLConfig ssl_config;
ssl_config.allowed_bad_certs = allowed_bad_certs;
ConfigureAlpn(endpoint, alpn_mode, network_anonymization_key,
*common_connect_job_params, ssl_config,
/*renego_allowed=*/true);
ssl_config.disable_cert_verification_network_fetches =
disable_cert_network_fetches;
// TODO(https://crbug.com/964642): Also enable 0-RTT for TLS proxies.
ssl_config.early_data_enabled =
*common_connect_job_params->enable_early_data;
if (proxy_chain.is_direct()) {
ssl_tcp_params = base::MakeRefCounted<TransportSocketParams>(
ToTransportEndpoint(endpoint), network_anonymization_key,
secure_dns_policy, resolution_callback,
SupportedProtocolsFromSSLConfig(ssl_config));
}
// TODO(crbug.com/1206799): Pass `endpoint` directly (preserving scheme
// when available)?
auto ssl_params = base::MakeRefCounted<SSLSocketParams>(
std::move(ssl_tcp_params), std::move(socks_params),
std::move(http_proxy_params), ToHostPortPair(endpoint), ssl_config,
privacy_mode, network_anonymization_key);
return ssl_connect_job_factory_->Create(
request_priority, socket_tag, common_connect_job_params,
std::move(ssl_params), delegate, /*net_log=*/nullptr);
}
// Only SSL/TLS-based endpoints have ALPN protocols.
if (proxy_chain.is_direct()) {
auto tcp_params = base::MakeRefCounted<TransportSocketParams>(
ToTransportEndpoint(endpoint), network_anonymization_key,
secure_dns_policy, resolution_callback, no_alpn_protocols);
return transport_connect_job_factory_->Create(
request_priority, socket_tag, common_connect_job_params, tcp_params,
delegate, /*net_log=*/nullptr);
}
const ProxyServer& last_proxy_server = proxy_chain.Last();
if (http_proxy_params) {
DCHECK(last_proxy_server.is_http_like());
return http_proxy_connect_job_factory_->Create(
request_priority, socket_tag, common_connect_job_params,
std::move(http_proxy_params), delegate, /*net_log=*/nullptr);
}
DCHECK(last_proxy_server.is_socks());
return socks_connect_job_factory_->Create(
request_priority, socket_tag, common_connect_job_params,
std::move(socks_params), delegate, /*net_log=*/nullptr);
}
} // namespace net