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