blob: dfc00da79df1f45c5269e57d86485e0977f1e3ac [file] [log] [blame]
// Copyright 2024 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_params_factory.h"
#include <optional>
#include <variant>
#include <vector>
#include "base/check.h"
#include "base/containers/flat_set.h"
#include "base/feature_list.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/string_util.h"
#include "net/base/features.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_params.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 "url/gurl.h"
#include "url/scheme_host_port.h"
namespace net {
namespace {
// 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`, copies `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 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(std::holds_alternative<url::SchemeHostPort>(endpoint));
if (alpn_mode == ConnectJobFactory::AlpnMode::kHttp11Only) {
ssl_config.alpn_protos = {NextProto::kProtoHTTP11};
ssl_config.application_settings =
*common_connect_job_params.application_settings;
} else {
DCHECK_EQ(alpn_mode, ConnectJobFactory::AlpnMode::kHttpAll);
DCHECK(std::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(
std::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 = {NextProto::kProtoHTTP11};
}
}
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);
}
HostPortPair ToHostPortPair(const ConnectJobFactory::Endpoint& endpoint) {
if (std::holds_alternative<url::SchemeHostPort>(endpoint)) {
return HostPortPair::FromSchemeHostPort(
std::get<url::SchemeHostPort>(endpoint));
}
DCHECK(
std::holds_alternative<ConnectJobFactory::SchemelessEndpoint>(endpoint));
return std::get<ConnectJobFactory::SchemelessEndpoint>(endpoint)
.host_port_pair;
}
TransportSocketParams::Endpoint ToTransportEndpoint(
const ConnectJobFactory::Endpoint& endpoint) {
if (std::holds_alternative<url::SchemeHostPort>(endpoint)) {
return std::get<url::SchemeHostPort>(endpoint);
}
DCHECK(
std::holds_alternative<ConnectJobFactory::SchemelessEndpoint>(endpoint));
return std::get<ConnectJobFactory::SchemelessEndpoint>(endpoint)
.host_port_pair;
}
bool UsingSsl(const ConnectJobFactory::Endpoint& endpoint) {
if (std::holds_alternative<url::SchemeHostPort>(endpoint)) {
return GURL::SchemeIsCryptographic(
base::ToLowerASCII(std::get<url::SchemeHostPort>(endpoint).scheme()));
}
DCHECK(
std::holds_alternative<ConnectJobFactory::SchemelessEndpoint>(endpoint));
return std::get<ConnectJobFactory::SchemelessEndpoint>(endpoint).using_ssl;
}
ConnectJobParams MakeSSLSocketParams(
ConnectJobParams params,
const HostPortPair& host_and_port,
const SSLConfig& ssl_config,
const NetworkAnonymizationKey& network_anonymization_key) {
return ConnectJobParams(base::MakeRefCounted<SSLSocketParams>(
std::move(params), host_and_port, ssl_config, network_anonymization_key));
}
// Recursively generate the params for a proxy at `host_port_pair` and the given
// index in the proxy chain. This proceeds from the end of the proxy chain back
// to the first proxy server.
ConnectJobParams CreateProxyParams(
HostPortPair host_port_pair,
bool should_tunnel,
const ConnectJobFactory::Endpoint& endpoint,
const ProxyChain& proxy_chain,
size_t proxy_chain_index,
const std::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
const OnHostResolutionCallback& resolution_callback,
const NetworkAnonymizationKey& endpoint_network_anonymization_key,
SecureDnsPolicy secure_dns_policy,
const CommonConnectJobParams* common_connect_job_params,
const NetworkAnonymizationKey& proxy_dns_network_anonymization_key) {
const ProxyServer& proxy_server =
proxy_chain.GetProxyServer(proxy_chain_index);
// If the requested session will be used to speak to a downstream proxy, then
// it need not be partitioned based on the ultimate destination's NAK. If the
// session is to the destination, then partition using that destination's NAK.
// This allows sharing of connections to proxies in multi-server proxy chains.
bool use_empty_nak =
!base::FeatureList::IsEnabled(net::features::kPartitionProxyChains) &&
proxy_chain_index < proxy_chain.length() - 1;
// Note that C++ extends the lifetime of this value such that the reference
// remains valid as long as the reference.
const NetworkAnonymizationKey& network_anonymization_key =
use_empty_nak ? NetworkAnonymizationKey()
: endpoint_network_anonymization_key;
// Set up the SSLConfig if using SSL to the proxy.
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);
proxy_server_ssl_config.proxy_chain = proxy_chain;
proxy_server_ssl_config.proxy_chain_index = proxy_chain_index;
proxy_server_ssl_config.session_usage = SessionUsage::kProxy;
}
// Create the nested parameters over which the connection to the proxy
// will be made.
ConnectJobParams params;
if (proxy_server.is_quic()) {
// If this and all proxies earlier in the chain are QUIC, then we can hand
// off the remainder of the proxy connecting work to the QuicSocketPool, so
// no further recursion is required. If any proxies earlier in the chain are
// not QUIC, then the chain is unsupported. Such ProxyChains cannot be
// constructed, so this is just a double-check.
for (size_t i = 0; i < proxy_chain_index; i++) {
CHECK(proxy_chain.GetProxyServer(i).is_quic());
}
return ConnectJobParams(base::MakeRefCounted<HttpProxySocketParams>(
std::move(proxy_server_ssl_config), host_port_pair, proxy_chain,
proxy_chain_index, should_tunnel, *proxy_annotation_tag,
network_anonymization_key, secure_dns_policy));
} else if (proxy_chain_index == 0) {
// At the beginning of the chain, create the only TransportSocketParams
// object, corresponding to the transport socket we want to create to the
// first proxy.
// TODO(crbug.com/40181080): 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.
params = ConnectJobParams(base::MakeRefCounted<TransportSocketParams>(
proxy_server.host_port_pair(), proxy_dns_network_anonymization_key,
secure_dns_policy, resolution_callback,
SupportedProtocolsFromSSLConfig(proxy_server_ssl_config)));
} else {
params = CreateProxyParams(
proxy_server.host_port_pair(), true, endpoint, proxy_chain,
proxy_chain_index - 1, proxy_annotation_tag, resolution_callback,
endpoint_network_anonymization_key, secure_dns_policy,
common_connect_job_params, proxy_dns_network_anonymization_key);
}
// For secure connections, wrap the underlying connection params in SSL
// params.
if (proxy_server.is_secure_http_like()) {
params =
MakeSSLSocketParams(std::move(params), proxy_server.host_port_pair(),
proxy_server_ssl_config, network_anonymization_key);
}
// Further wrap the underlying connection params, or the SSL params wrapping
// them, with the proxy params.
if (proxy_server.is_http_like()) {
CHECK(!proxy_server.is_quic());
params = ConnectJobParams(base::MakeRefCounted<HttpProxySocketParams>(
std::move(params), host_port_pair, proxy_chain, proxy_chain_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/40181080): Pass `endpoint` directly (preserving scheme
// when available)?
params = ConnectJobParams(base::MakeRefCounted<SOCKSSocketParams>(
std::move(params), proxy_server.scheme() == ProxyServer::SCHEME_SOCKS5,
ToHostPortPair(endpoint), network_anonymization_key,
*proxy_annotation_tag));
}
return params;
}
} // namespace
ConnectJobParams ConstructConnectJobParams(
const ConnectJobFactory::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,
const NetworkAnonymizationKey& endpoint_network_anonymization_key,
SecureDnsPolicy secure_dns_policy,
bool disable_cert_network_fetches,
const CommonConnectJobParams* common_connect_job_params,
const NetworkAnonymizationKey& proxy_dns_network_anonymization_key) {
DCHECK(proxy_chain.IsValid());
// Set up `ssl_config` if using SSL to the endpoint.
SSLConfig ssl_config;
if (UsingSsl(endpoint)) {
ssl_config.allowed_bad_certs = allowed_bad_certs;
ssl_config.privacy_mode = privacy_mode;
ConfigureAlpn(endpoint, alpn_mode, endpoint_network_anonymization_key,
*common_connect_job_params, ssl_config,
/*renego_allowed=*/true);
ssl_config.disable_cert_verification_network_fetches =
disable_cert_network_fetches;
// TODO(crbug.com/41459647): Also enable 0-RTT for TLS proxies.
ssl_config.early_data_enabled =
*common_connect_job_params->enable_early_data;
ssl_config.proxy_chain = proxy_chain;
ssl_config.proxy_chain_index = proxy_chain.length();
ssl_config.session_usage = SessionUsage::kDestination;
}
// Create the nested parameters over which the connection to the endpoint
// will be made.
ConnectJobParams params;
if (proxy_chain.is_direct()) {
params = ConnectJobParams(base::MakeRefCounted<TransportSocketParams>(
ToTransportEndpoint(endpoint), endpoint_network_anonymization_key,
secure_dns_policy, resolution_callback,
SupportedProtocolsFromSSLConfig(ssl_config)));
} else {
bool should_tunnel = force_tunnel || UsingSsl(endpoint) ||
!proxy_chain.is_get_to_proxy_allowed();
// Begin creating params for the last proxy in the chain. This will
// recursively create params "backward" through the chain to the first.
params = CreateProxyParams(
ToHostPortPair(endpoint), should_tunnel, endpoint, proxy_chain,
/*proxy_chain_index=*/proxy_chain.length() - 1, proxy_annotation_tag,
resolution_callback, endpoint_network_anonymization_key,
secure_dns_policy, common_connect_job_params,
proxy_dns_network_anonymization_key);
}
if (UsingSsl(endpoint)) {
// Wrap the final params (which includes connections through zero or more
// proxies) in SSLSocketParams to handle SSL to to the endpoint.
// TODO(crbug.com/40181080): Pass `endpoint` directly (preserving scheme
// when available)?
params =
MakeSSLSocketParams(std::move(params), ToHostPortPair(endpoint),
ssl_config, endpoint_network_anonymization_key);
}
return params;
}
} // namespace net