1 // Copyright 2021 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_factory.h"
6
7 #include <memory>
8 #include <utility>
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.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/optional.h"
30 #include "third_party/abseil-cpp/absl/types/variant.h"
31 #include "url/gurl.h"
32 #include "url/scheme_host_port.h"
33
34 namespace net {
35
36 namespace {
37
38 template <typename T>
CreateFactoryIfNull(std::unique_ptr<T> in)39 std::unique_ptr<T> CreateFactoryIfNull(std::unique_ptr<T> in) {
40 if (in)
41 return in;
42 return std::make_unique<T>();
43 }
44
UsingSsl(const ConnectJobFactory::Endpoint & endpoint)45 bool UsingSsl(const ConnectJobFactory::Endpoint& endpoint) {
46 if (absl::holds_alternative<url::SchemeHostPort>(endpoint)) {
47 return GURL::SchemeIsCryptographic(
48 base::ToLowerASCII(absl::get<url::SchemeHostPort>(endpoint).scheme()));
49 }
50
51 DCHECK(
52 absl::holds_alternative<ConnectJobFactory::SchemelessEndpoint>(endpoint));
53 return absl::get<ConnectJobFactory::SchemelessEndpoint>(endpoint).using_ssl;
54 }
55
ToHostPortPair(const ConnectJobFactory::Endpoint & endpoint)56 HostPortPair ToHostPortPair(const ConnectJobFactory::Endpoint& endpoint) {
57 if (absl::holds_alternative<url::SchemeHostPort>(endpoint)) {
58 return HostPortPair::FromSchemeHostPort(
59 absl::get<url::SchemeHostPort>(endpoint));
60 }
61
62 DCHECK(
63 absl::holds_alternative<ConnectJobFactory::SchemelessEndpoint>(endpoint));
64 return absl::get<ConnectJobFactory::SchemelessEndpoint>(endpoint)
65 .host_port_pair;
66 }
67
ToTransportEndpoint(const ConnectJobFactory::Endpoint & endpoint)68 TransportSocketParams::Endpoint ToTransportEndpoint(
69 const ConnectJobFactory::Endpoint& endpoint) {
70 if (absl::holds_alternative<url::SchemeHostPort>(endpoint))
71 return absl::get<url::SchemeHostPort>(endpoint);
72
73 DCHECK(
74 absl::holds_alternative<ConnectJobFactory::SchemelessEndpoint>(endpoint));
75 return absl::get<ConnectJobFactory::SchemelessEndpoint>(endpoint)
76 .host_port_pair;
77 }
78
SupportedProtocolsFromSSLConfig(const SSLConfig & config)79 base::flat_set<std::string> SupportedProtocolsFromSSLConfig(
80 const SSLConfig& config) {
81 // We convert because `SSLConfig` uses `NextProto` for ALPN protocols while
82 // `TransportConnectJob` and DNS logic needs `std::string`. See
83 // https://crbug.com/1286835.
84 return base::MakeFlatSet<std::string>(config.alpn_protos, /*comp=*/{},
85 NextProtoToString);
86 }
87
MaybeForceHttp11(const ProxyServer & proxy_server,const CommonConnectJobParams * common_connect_job_params,const NetworkAnonymizationKey & network_anonymization_key,SSLConfig * proxy_server_ssl_config)88 void MaybeForceHttp11(const ProxyServer& proxy_server,
89 const CommonConnectJobParams* common_connect_job_params,
90 const NetworkAnonymizationKey& network_anonymization_key,
91 SSLConfig* proxy_server_ssl_config) {
92 HttpServerProperties* http_server_properties =
93 common_connect_job_params->http_server_properties;
94 if (http_server_properties) {
95 if (proxy_server.is_https()) {
96 http_server_properties->MaybeForceHTTP11(
97 url::SchemeHostPort(url::kHttpsScheme,
98 proxy_server.host_port_pair().host(),
99 proxy_server.host_port_pair().port()),
100 network_anonymization_key, proxy_server_ssl_config);
101 }
102 }
103 }
104
105 } // namespace
106
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)107 ConnectJobFactory::ConnectJobFactory(
108 std::unique_ptr<HttpProxyConnectJob::Factory>
109 http_proxy_connect_job_factory,
110 std::unique_ptr<SOCKSConnectJob::Factory> socks_connect_job_factory,
111 std::unique_ptr<SSLConnectJob::Factory> ssl_connect_job_factory,
112 std::unique_ptr<TransportConnectJob::Factory> transport_connect_job_factory)
113 : http_proxy_connect_job_factory_(
114 CreateFactoryIfNull(std::move(http_proxy_connect_job_factory))),
115 socks_connect_job_factory_(
116 CreateFactoryIfNull(std::move(socks_connect_job_factory))),
117 ssl_connect_job_factory_(
118 CreateFactoryIfNull(std::move(ssl_connect_job_factory))),
119 transport_connect_job_factory_(
120 CreateFactoryIfNull(std::move(transport_connect_job_factory))) {}
121
122 ConnectJobFactory::~ConnectJobFactory() = default;
123
CreateConnectJob(url::SchemeHostPort endpoint,const ProxyChain & proxy_chain,const absl::optional<NetworkTrafficAnnotationTag> & proxy_annotation_tag,const SSLConfig * ssl_config_for_origin,const SSLConfig * base_ssl_config_for_proxies,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) const124 std::unique_ptr<ConnectJob> ConnectJobFactory::CreateConnectJob(
125 url::SchemeHostPort endpoint,
126 const ProxyChain& proxy_chain,
127 const absl::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
128 const SSLConfig* ssl_config_for_origin,
129 const SSLConfig* base_ssl_config_for_proxies,
130 bool force_tunnel,
131 PrivacyMode privacy_mode,
132 const OnHostResolutionCallback& resolution_callback,
133 RequestPriority request_priority,
134 SocketTag socket_tag,
135 const NetworkAnonymizationKey& network_anonymization_key,
136 SecureDnsPolicy secure_dns_policy,
137 const CommonConnectJobParams* common_connect_job_params,
138 ConnectJob::Delegate* delegate) const {
139 return CreateConnectJob(
140 Endpoint(std::move(endpoint)), proxy_chain, proxy_annotation_tag,
141 ssl_config_for_origin, base_ssl_config_for_proxies, force_tunnel,
142 privacy_mode, resolution_callback, request_priority, socket_tag,
143 network_anonymization_key, secure_dns_policy, common_connect_job_params,
144 delegate);
145 }
146
CreateConnectJob(bool using_ssl,HostPortPair endpoint,const ProxyChain & proxy_chain,const absl::optional<NetworkTrafficAnnotationTag> & proxy_annotation_tag,const SSLConfig * ssl_config_for_origin,const SSLConfig * base_ssl_config_for_proxies,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) const147 std::unique_ptr<ConnectJob> ConnectJobFactory::CreateConnectJob(
148 bool using_ssl,
149 HostPortPair endpoint,
150 const ProxyChain& proxy_chain,
151 const absl::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
152 const SSLConfig* ssl_config_for_origin,
153 const SSLConfig* base_ssl_config_for_proxies,
154 bool force_tunnel,
155 PrivacyMode privacy_mode,
156 const OnHostResolutionCallback& resolution_callback,
157 RequestPriority request_priority,
158 SocketTag socket_tag,
159 const NetworkAnonymizationKey& network_anonymization_key,
160 SecureDnsPolicy secure_dns_policy,
161 const CommonConnectJobParams* common_connect_job_params,
162 ConnectJob::Delegate* delegate) const {
163 SchemelessEndpoint schemeless_endpoint{using_ssl, std::move(endpoint)};
164 return CreateConnectJob(
165 std::move(schemeless_endpoint), proxy_chain, proxy_annotation_tag,
166 ssl_config_for_origin, base_ssl_config_for_proxies, force_tunnel,
167 privacy_mode, resolution_callback, request_priority, socket_tag,
168 network_anonymization_key, secure_dns_policy, common_connect_job_params,
169 delegate);
170 }
171
CreateConnectJob(Endpoint endpoint,const ProxyChain & proxy_chain,const absl::optional<NetworkTrafficAnnotationTag> & proxy_annotation_tag,const SSLConfig * ssl_config_for_origin,const SSLConfig * base_ssl_config_for_proxies,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) const172 std::unique_ptr<ConnectJob> ConnectJobFactory::CreateConnectJob(
173 Endpoint endpoint,
174 const ProxyChain& proxy_chain,
175 const absl::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
176 const SSLConfig* ssl_config_for_origin,
177 const SSLConfig* base_ssl_config_for_proxies,
178 bool force_tunnel,
179 PrivacyMode privacy_mode,
180 const OnHostResolutionCallback& resolution_callback,
181 RequestPriority request_priority,
182 SocketTag socket_tag,
183 const NetworkAnonymizationKey& network_anonymization_key,
184 SecureDnsPolicy secure_dns_policy,
185 const CommonConnectJobParams* common_connect_job_params,
186 ConnectJob::Delegate* delegate) const {
187 scoped_refptr<HttpProxySocketParams> http_proxy_params;
188 scoped_refptr<SOCKSSocketParams> socks_params;
189 base::flat_set<std::string> no_alpn_protocols;
190
191 DCHECK(proxy_chain.IsValid());
192 if (!proxy_chain.is_direct()) {
193 // The first iteration of this loop is taken for all types of proxies and
194 // creates a TransportSocketParams and other socket params based on the
195 // proxy type. For nested proxies, we then create additional SSLSocketParam
196 // and HttpProxySocketParam objects for the remaining hops. This is done by
197 // working backwards through the proxy chain and creating socket params
198 // such that connect jobs will be created recursively with dependencies in
199 // the correct order (in other words, the inner-most connect job will
200 // establish a connection to the first proxy, and then that connection
201 // will get used to establish a connection to the second proxy).
202 for (size_t proxy_index = 0; proxy_index < proxy_chain.length();
203 ++proxy_index) {
204 const ProxyServer& proxy_server = proxy_chain.GetProxyServer(proxy_index);
205
206 SSLConfig proxy_server_ssl_config;
207 if (proxy_server.is_secure_http_like()) {
208 DCHECK(base_ssl_config_for_proxies);
209 proxy_server_ssl_config = *base_ssl_config_for_proxies;
210 // Disable cert verification network fetches for secure proxies, since
211 // those network requests are probably going to need to go through the
212 // proxy chain too.
213 //
214 // Any proxy-specific SSL behavior here should also be configured for
215 // QUIC proxies.
216 //
217 proxy_server_ssl_config.disable_cert_verification_network_fetches =
218 true;
219 MaybeForceHttp11(proxy_server, common_connect_job_params,
220 network_anonymization_key, &proxy_server_ssl_config);
221 }
222
223 scoped_refptr<TransportSocketParams> proxy_tcp_params;
224 if (proxy_index == 0) {
225 // In the first iteration create the only TransportSocketParams object,
226 // corresponding to the transport socket we want to create to the first
227 // proxy.
228 // TODO(crbug.com/1206799): 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 proxy_tcp_params = base::MakeRefCounted<TransportSocketParams>(
232 proxy_server.host_port_pair(), proxy_dns_network_anonymization_key_,
233 secure_dns_policy, resolution_callback,
234 proxy_server.is_secure_http_like()
235 ? SupportedProtocolsFromSSLConfig(proxy_server_ssl_config)
236 : no_alpn_protocols);
237 } else {
238 // TODO(https://crbug.com/1491092): For now we will assume that proxy
239 // chains with multiple proxies must all use HTTPS.
240 CHECK(http_proxy_params);
241 CHECK(http_proxy_params->ssl_params());
242 CHECK(
243 proxy_chain.GetProxyServer(proxy_index - 1).is_secure_http_like());
244 }
245
246 if (proxy_server.is_http_like()) {
247 scoped_refptr<SSLSocketParams> ssl_params;
248 if (proxy_server.is_secure_http_like()) {
249 // Set `ssl_params`, and unset `proxy_tcp_params`.
250 ssl_params = base::MakeRefCounted<SSLSocketParams>(
251 std::move(proxy_tcp_params), /*socks_proxy_params=*/nullptr,
252 std::move(http_proxy_params), proxy_server.host_port_pair(),
253 proxy_server_ssl_config, PRIVACY_MODE_DISABLED,
254 network_anonymization_key);
255 proxy_tcp_params = nullptr;
256 }
257
258 // The endpoint parameter for this HttpProxySocketParams, which is what
259 // we will CONNECT to, should correspond to either `endpoint` (for
260 // one-hop proxies) or the proxy server at index 1 (for n-hop proxies).
261 HostPortPair connect_host_port_pair;
262 bool should_tunnel;
263 if (proxy_index + 1 == proxy_chain.length()) {
264 connect_host_port_pair = ToHostPortPair(endpoint);
265 should_tunnel = force_tunnel || UsingSsl(endpoint);
266 } else {
267 const auto& next_proxy_server =
268 proxy_chain.GetProxyServer(proxy_index + 1);
269 connect_host_port_pair = next_proxy_server.host_port_pair();
270 // TODO(https://crbug.com/1491092): For now we will assume that proxy
271 // chains with multiple proxies must all use HTTPS.
272 CHECK(next_proxy_server.is_secure_http_like());
273 should_tunnel = true;
274 }
275
276 // TODO(crbug.com/1206799): Pass `endpoint` directly (preserving
277 // scheme when available)?
278 http_proxy_params = base::MakeRefCounted<HttpProxySocketParams>(
279 std::move(proxy_tcp_params), std::move(ssl_params),
280 connect_host_port_pair, proxy_chain, proxy_index, should_tunnel,
281 *proxy_annotation_tag, network_anonymization_key,
282 secure_dns_policy);
283 } else {
284 DCHECK(proxy_server.is_socks());
285 DCHECK_EQ(1u, proxy_chain.length());
286 // TODO(crbug.com/1206799): Pass `endpoint` directly (preserving scheme
287 // when available)?
288 socks_params = base::MakeRefCounted<SOCKSSocketParams>(
289 std::move(proxy_tcp_params),
290 proxy_server.scheme() == ProxyServer::SCHEME_SOCKS5,
291 ToHostPortPair(endpoint), network_anonymization_key,
292 *proxy_annotation_tag);
293 }
294 }
295 }
296
297 // Deal with SSL - which layers on top of any given proxy.
298 if (UsingSsl(endpoint)) {
299 DCHECK(ssl_config_for_origin);
300 scoped_refptr<TransportSocketParams> ssl_tcp_params;
301 if (proxy_chain.is_direct()) {
302 ssl_tcp_params = base::MakeRefCounted<TransportSocketParams>(
303 ToTransportEndpoint(endpoint), network_anonymization_key,
304 secure_dns_policy, resolution_callback,
305 SupportedProtocolsFromSSLConfig(*ssl_config_for_origin));
306 }
307 // TODO(crbug.com/1206799): Pass `endpoint` directly (preserving scheme
308 // when available)?
309 auto ssl_params = base::MakeRefCounted<SSLSocketParams>(
310 std::move(ssl_tcp_params), std::move(socks_params),
311 std::move(http_proxy_params), ToHostPortPair(endpoint),
312 *ssl_config_for_origin, privacy_mode, network_anonymization_key);
313 return ssl_connect_job_factory_->Create(
314 request_priority, socket_tag, common_connect_job_params,
315 std::move(ssl_params), delegate, /*net_log=*/nullptr);
316 }
317
318 // Only SSL/TLS-based endpoints have ALPN protocols.
319 if (proxy_chain.is_direct()) {
320 auto tcp_params = base::MakeRefCounted<TransportSocketParams>(
321 ToTransportEndpoint(endpoint), network_anonymization_key,
322 secure_dns_policy, resolution_callback, no_alpn_protocols);
323 return transport_connect_job_factory_->Create(
324 request_priority, socket_tag, common_connect_job_params, tcp_params,
325 delegate, /*net_log=*/nullptr);
326 }
327
328 const ProxyServer& first_proxy_server =
329 proxy_chain.GetProxyServer(/*chain_index=*/0);
330 if (first_proxy_server.is_http_like()) {
331 return http_proxy_connect_job_factory_->Create(
332 request_priority, socket_tag, common_connect_job_params,
333 std::move(http_proxy_params), delegate, /*net_log=*/nullptr);
334 }
335
336 DCHECK(first_proxy_server.is_socks());
337 return socks_connect_job_factory_->Create(
338 request_priority, socket_tag, common_connect_job_params,
339 std::move(socks_params), delegate, /*net_log=*/nullptr);
340 }
341
342 } // namespace net
343