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