• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/socket/connect_job_params_factory.h"
6 
7 #include <optional>
8 #include <vector>
9 
10 #include "base/check.h"
11 #include "base/containers/flat_set.h"
12 #include "base/feature_list.h"
13 #include "base/memory/scoped_refptr.h"
14 #include "net/base/features.h"
15 #include "net/base/host_port_pair.h"
16 #include "net/base/network_anonymization_key.h"
17 #include "net/base/privacy_mode.h"
18 #include "net/base/proxy_chain.h"
19 #include "net/base/proxy_server.h"
20 #include "net/base/request_priority.h"
21 #include "net/dns/public/secure_dns_policy.h"
22 #include "net/http/http_proxy_connect_job.h"
23 #include "net/socket/connect_job_params.h"
24 #include "net/socket/next_proto.h"
25 #include "net/socket/socket_tag.h"
26 #include "net/socket/socks_connect_job.h"
27 #include "net/socket/ssl_connect_job.h"
28 #include "net/socket/transport_connect_job.h"
29 #include "net/ssl/ssl_config.h"
30 #include "net/traffic_annotation/network_traffic_annotation.h"
31 #include "third_party/abseil-cpp/absl/types/variant.h"
32 #include "url/gurl.h"
33 #include "url/scheme_host_port.h"
34 
35 namespace net {
36 
37 namespace {
38 
39 // Populates `ssl_config's` ALPN-related fields. Namely, `alpn_protos`,
40 // `application_settings`, `renego_allowed_default`, and
41 // `renego_allowed_for_protos`.
42 //
43 // In the case of `AlpnMode::kDisabled`, clears all of the fields.
44 //
45 // In the case of `AlpnMode::kHttp11Only`, sets `alpn_protos` to only allow
46 // HTTP/1.1 negotiation.
47 //
48 // In the case of `AlpnMode::kHttpAll`, copies `alpn_protos` from
49 // `common_connect_job_params`, and gives `HttpServerProperties` a chance to
50 // force use of HTTP/1.1 only.
51 //
52 // If `alpn_mode` is not `AlpnMode::kDisabled`, then `server` must be a
53 // `SchemeHostPort`, as it makes no sense to negotiate ALPN when the scheme
54 // isn't known.
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)55 void ConfigureAlpn(const ConnectJobFactory::Endpoint& endpoint,
56                    ConnectJobFactory::AlpnMode alpn_mode,
57                    const NetworkAnonymizationKey& network_anonymization_key,
58                    const CommonConnectJobParams& common_connect_job_params,
59                    SSLConfig& ssl_config,
60                    bool renego_allowed) {
61   if (alpn_mode == ConnectJobFactory::AlpnMode::kDisabled) {
62     ssl_config.alpn_protos = {};
63     ssl_config.application_settings = {};
64     ssl_config.renego_allowed_default = false;
65     return;
66   }
67 
68   DCHECK(absl::holds_alternative<url::SchemeHostPort>(endpoint));
69 
70   if (alpn_mode == ConnectJobFactory::AlpnMode::kHttp11Only) {
71     ssl_config.alpn_protos = {kProtoHTTP11};
72     ssl_config.application_settings =
73         *common_connect_job_params.application_settings;
74   } else {
75     DCHECK_EQ(alpn_mode, ConnectJobFactory::AlpnMode::kHttpAll);
76     DCHECK(absl::holds_alternative<url::SchemeHostPort>(endpoint));
77     ssl_config.alpn_protos = *common_connect_job_params.alpn_protos;
78     ssl_config.application_settings =
79         *common_connect_job_params.application_settings;
80     if (common_connect_job_params.http_server_properties) {
81       common_connect_job_params.http_server_properties->MaybeForceHTTP11(
82           absl::get<url::SchemeHostPort>(endpoint), network_anonymization_key,
83           &ssl_config);
84     }
85   }
86 
87   // Prior to HTTP/2 and SPDY, some servers used TLS renegotiation to request
88   // TLS client authentication after the HTTP request was sent. Allow
89   // renegotiation for only those connections.
90   //
91   // Note that this does NOT implement the provision in
92   // https://http2.github.io/http2-spec/#rfc.section.9.2.1 which allows the
93   // server to request a renegotiation immediately before sending the
94   // connection preface as waiting for the preface would cost the round trip
95   // that False Start otherwise saves.
96   ssl_config.renego_allowed_default = renego_allowed;
97   if (renego_allowed) {
98     ssl_config.renego_allowed_for_protos = {kProtoHTTP11};
99   }
100 }
101 
SupportedProtocolsFromSSLConfig(const SSLConfig & config)102 base::flat_set<std::string> SupportedProtocolsFromSSLConfig(
103     const SSLConfig& config) {
104   // We convert because `SSLConfig` uses `NextProto` for ALPN protocols while
105   // `TransportConnectJob` and DNS logic needs `std::string`. See
106   // https://crbug.com/1286835.
107   return base::MakeFlatSet<std::string>(config.alpn_protos, /*comp=*/{},
108                                         NextProtoToString);
109 }
110 
ToHostPortPair(const ConnectJobFactory::Endpoint & endpoint)111 HostPortPair ToHostPortPair(const ConnectJobFactory::Endpoint& endpoint) {
112   if (absl::holds_alternative<url::SchemeHostPort>(endpoint)) {
113     return HostPortPair::FromSchemeHostPort(
114         absl::get<url::SchemeHostPort>(endpoint));
115   }
116 
117   DCHECK(
118       absl::holds_alternative<ConnectJobFactory::SchemelessEndpoint>(endpoint));
119   return absl::get<ConnectJobFactory::SchemelessEndpoint>(endpoint)
120       .host_port_pair;
121 }
122 
ToTransportEndpoint(const ConnectJobFactory::Endpoint & endpoint)123 TransportSocketParams::Endpoint ToTransportEndpoint(
124     const ConnectJobFactory::Endpoint& endpoint) {
125   if (absl::holds_alternative<url::SchemeHostPort>(endpoint)) {
126     return absl::get<url::SchemeHostPort>(endpoint);
127   }
128 
129   DCHECK(
130       absl::holds_alternative<ConnectJobFactory::SchemelessEndpoint>(endpoint));
131   return absl::get<ConnectJobFactory::SchemelessEndpoint>(endpoint)
132       .host_port_pair;
133 }
134 
UsingSsl(const ConnectJobFactory::Endpoint & endpoint)135 bool UsingSsl(const ConnectJobFactory::Endpoint& endpoint) {
136   if (absl::holds_alternative<url::SchemeHostPort>(endpoint)) {
137     return GURL::SchemeIsCryptographic(
138         base::ToLowerASCII(absl::get<url::SchemeHostPort>(endpoint).scheme()));
139   }
140 
141   DCHECK(
142       absl::holds_alternative<ConnectJobFactory::SchemelessEndpoint>(endpoint));
143   return absl::get<ConnectJobFactory::SchemelessEndpoint>(endpoint).using_ssl;
144 }
145 
MakeSSLSocketParams(ConnectJobParams params,const HostPortPair & host_and_port,const SSLConfig & ssl_config,const NetworkAnonymizationKey & network_anonymization_key)146 ConnectJobParams MakeSSLSocketParams(
147     ConnectJobParams params,
148     const HostPortPair& host_and_port,
149     const SSLConfig& ssl_config,
150     const NetworkAnonymizationKey& network_anonymization_key) {
151   return ConnectJobParams(base::MakeRefCounted<SSLSocketParams>(
152       std::move(params), host_and_port, ssl_config, network_anonymization_key));
153 }
154 
155 // Recursively generate the params for a proxy at `host_port_pair` and the given
156 // index in the proxy chain. This proceeds from the end of the proxy chain back
157 // to the first proxy server.
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)158 ConnectJobParams CreateProxyParams(
159     HostPortPair host_port_pair,
160     bool should_tunnel,
161     const ConnectJobFactory::Endpoint& endpoint,
162     const ProxyChain& proxy_chain,
163     size_t proxy_chain_index,
164     const std::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
165     const OnHostResolutionCallback& resolution_callback,
166     const NetworkAnonymizationKey& endpoint_network_anonymization_key,
167     SecureDnsPolicy secure_dns_policy,
168     const CommonConnectJobParams* common_connect_job_params,
169     const NetworkAnonymizationKey& proxy_dns_network_anonymization_key) {
170   const ProxyServer& proxy_server =
171       proxy_chain.GetProxyServer(proxy_chain_index);
172 
173   // If the requested session will be used to speak to a downstream proxy, then
174   // it need not be partitioned based on the ultimate destination's NAK. If the
175   // session is to the destination, then partition using that destination's NAK.
176   // This allows sharing of connections to proxies in multi-server proxy chains.
177   bool use_empty_nak =
178       !base::FeatureList::IsEnabled(net::features::kPartitionProxyChains) &&
179       proxy_chain_index < proxy_chain.length() - 1;
180   // Note that C++ extends the lifetime of this value such that the reference
181   // remains valid as long as the reference.
182   const NetworkAnonymizationKey& network_anonymization_key =
183       use_empty_nak ? NetworkAnonymizationKey()
184                     : endpoint_network_anonymization_key;
185 
186   // Set up the SSLConfig if using SSL to the proxy.
187   SSLConfig proxy_server_ssl_config;
188 
189   if (proxy_server.is_secure_http_like()) {
190     // Disable cert verification network fetches for secure proxies, since
191     // those network requests are probably going to need to go through the
192     // proxy chain too.
193     //
194     // Any proxy-specific SSL behavior here should also be configured for
195     // QUIC proxies.
196     proxy_server_ssl_config.disable_cert_verification_network_fetches = true;
197     ConfigureAlpn(url::SchemeHostPort(url::kHttpsScheme,
198                                       proxy_server.host_port_pair().host(),
199                                       proxy_server.host_port_pair().port()),
200                   // Always enable ALPN for proxies.
201                   ConnectJobFactory::AlpnMode::kHttpAll,
202                   network_anonymization_key, *common_connect_job_params,
203                   proxy_server_ssl_config,
204                   /*renego_allowed=*/false);
205   }
206 
207   // Create the nested parameters over which the connection to the proxy
208   // will be made.
209   ConnectJobParams params;
210 
211   if (proxy_server.is_quic()) {
212     // If this and all proxies earlier in the chain are QUIC, then we can hand
213     // off the remainder of the proxy connecting work to the QuicSocketPool, so
214     // no further recursion is required. If any proxies earlier in the chain are
215     // not QUIC, then the chain is unsupported. Such ProxyChains cannot be
216     // constructed, so this is just a double-check.
217     for (size_t i = 0; i < proxy_chain_index; i++) {
218       CHECK(proxy_chain.GetProxyServer(i).is_quic());
219     }
220     return ConnectJobParams(base::MakeRefCounted<HttpProxySocketParams>(
221         std::move(proxy_server_ssl_config), host_port_pair, proxy_chain,
222         proxy_chain_index, should_tunnel, *proxy_annotation_tag,
223         network_anonymization_key, secure_dns_policy));
224   } else if (proxy_chain_index == 0) {
225     // At the beginning of the chain, create the only TransportSocketParams
226     // object, corresponding to the transport socket we want to create to the
227     // first proxy.
228     // TODO(crbug.com/40181080): For an http-like proxy, should this pass a
229     // `SchemeHostPort`, so proxies can participate in ECH? Note doing so
230     // with `SCHEME_HTTP` requires handling the HTTPS record upgrade.
231     params = ConnectJobParams(base::MakeRefCounted<TransportSocketParams>(
232         proxy_server.host_port_pair(), proxy_dns_network_anonymization_key,
233         secure_dns_policy, resolution_callback,
234         SupportedProtocolsFromSSLConfig(proxy_server_ssl_config)));
235   } else {
236     params = CreateProxyParams(
237         proxy_server.host_port_pair(), true, endpoint, proxy_chain,
238         proxy_chain_index - 1, proxy_annotation_tag, resolution_callback,
239         endpoint_network_anonymization_key, secure_dns_policy,
240         common_connect_job_params, proxy_dns_network_anonymization_key);
241   }
242 
243   // For secure connections, wrap the underlying connection params in SSL
244   // params.
245   if (proxy_server.is_secure_http_like()) {
246     params =
247         MakeSSLSocketParams(std::move(params), proxy_server.host_port_pair(),
248                             proxy_server_ssl_config, network_anonymization_key);
249   }
250 
251   // Further wrap the underlying connection params, or the SSL params wrapping
252   // them, with the proxy params.
253   if (proxy_server.is_http_like()) {
254     CHECK(!proxy_server.is_quic());
255     params = ConnectJobParams(base::MakeRefCounted<HttpProxySocketParams>(
256         std::move(params), host_port_pair, proxy_chain, proxy_chain_index,
257         should_tunnel, *proxy_annotation_tag, network_anonymization_key,
258         secure_dns_policy));
259   } else {
260     DCHECK(proxy_server.is_socks());
261     DCHECK_EQ(1u, proxy_chain.length());
262     // TODO(crbug.com/40181080): Pass `endpoint` directly (preserving scheme
263     // when available)?
264     params = ConnectJobParams(base::MakeRefCounted<SOCKSSocketParams>(
265         std::move(params), proxy_server.scheme() == ProxyServer::SCHEME_SOCKS5,
266         ToHostPortPair(endpoint), network_anonymization_key,
267         *proxy_annotation_tag));
268   }
269 
270   return params;
271 }
272 
273 }  // namespace
274 
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)275 ConnectJobParams ConstructConnectJobParams(
276     const ConnectJobFactory::Endpoint& endpoint,
277     const ProxyChain& proxy_chain,
278     const std::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
279     const std::vector<SSLConfig::CertAndStatus>& allowed_bad_certs,
280     ConnectJobFactory::AlpnMode alpn_mode,
281     bool force_tunnel,
282     PrivacyMode privacy_mode,
283     const OnHostResolutionCallback& resolution_callback,
284     const NetworkAnonymizationKey& endpoint_network_anonymization_key,
285     SecureDnsPolicy secure_dns_policy,
286     bool disable_cert_network_fetches,
287     const CommonConnectJobParams* common_connect_job_params,
288     const NetworkAnonymizationKey& proxy_dns_network_anonymization_key) {
289   DCHECK(proxy_chain.IsValid());
290 
291   // Set up `ssl_config` if using SSL to the endpoint.
292   SSLConfig ssl_config;
293   if (UsingSsl(endpoint)) {
294     ssl_config.allowed_bad_certs = allowed_bad_certs;
295     ssl_config.privacy_mode = privacy_mode;
296 
297     ConfigureAlpn(endpoint, alpn_mode, endpoint_network_anonymization_key,
298                   *common_connect_job_params, ssl_config,
299                   /*renego_allowed=*/true);
300 
301     ssl_config.disable_cert_verification_network_fetches =
302         disable_cert_network_fetches;
303 
304     // TODO(crbug.com/41459647): Also enable 0-RTT for TLS proxies.
305     ssl_config.early_data_enabled =
306         *common_connect_job_params->enable_early_data;
307   }
308 
309   // Create the nested parameters over which the connection to the endpoint
310   // will be made.
311   ConnectJobParams params;
312   if (proxy_chain.is_direct()) {
313     params = ConnectJobParams(base::MakeRefCounted<TransportSocketParams>(
314         ToTransportEndpoint(endpoint), endpoint_network_anonymization_key,
315         secure_dns_policy, resolution_callback,
316         SupportedProtocolsFromSSLConfig(ssl_config)));
317   } else {
318     bool should_tunnel = force_tunnel || UsingSsl(endpoint) ||
319                          !proxy_chain.is_get_to_proxy_allowed();
320     // Begin creating params for the last proxy in the chain. This will
321     // recursively create params "backward" through the chain to the first.
322     params = CreateProxyParams(
323         ToHostPortPair(endpoint), should_tunnel, endpoint, proxy_chain,
324         /*proxy_chain_index=*/proxy_chain.length() - 1, proxy_annotation_tag,
325         resolution_callback, endpoint_network_anonymization_key,
326         secure_dns_policy, common_connect_job_params,
327         proxy_dns_network_anonymization_key);
328   }
329 
330   if (UsingSsl(endpoint)) {
331     // Wrap the final params (which includes connections through zero or more
332     // proxies) in SSLSocketParams to handle SSL to to the endpoint.
333     // TODO(crbug.com/40181080): Pass `endpoint` directly (preserving scheme
334     // when available)?
335     params =
336         MakeSSLSocketParams(std::move(params), ToHostPortPair(endpoint),
337                             ssl_config, endpoint_network_anonymization_key);
338   }
339 
340   return params;
341 }
342 
343 }  // namespace net
344