1 // Copyright 2019 The Chromium Authors. All rights reserved.
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 "services/network/sec_header_helpers.h"
6
7 #include <algorithm>
8 #include <string>
9
10 #include "base/feature_list.h"
11 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
12 #include "net/http/http_request_headers.h"
13 #include "net/url_request/url_request.h"
14 #include "services/network/public/cpp/cors/origin_access_list.h"
15 #include "services/network/public/cpp/features.h"
16 #include "services/network/public/cpp/initiator_lock_compatibility.h"
17 #include "services/network/public/cpp/is_potentially_trustworthy.h"
18 #include "services/network/public/cpp/request_destination.h"
19 #include "services/network/public/cpp/request_mode.h"
20 #include "services/network/public/mojom/fetch_api.mojom.h"
21 #include "services/network/public/mojom/network_context.mojom.h"
22
23 namespace network {
24
25 namespace {
26
27 const char kSecFetchMode[] = "Sec-Fetch-Mode";
28 const char kSecFetchSite[] = "Sec-Fetch-Site";
29 const char kSecFetchUser[] = "Sec-Fetch-User";
30 const char kSecFetchDest[] = "Sec-Fetch-Dest";
31
32 // Sec-Fetch-Site infrastructure:
33 //
34 // Note that the order of enum values below is significant - it is important for
35 // std::max invocations that kSameOrigin < kSameSite < kCrossSite.
36 enum class SecFetchSiteValue {
37 kNoOrigin,
38 kSameOrigin,
39 kSameSite,
40 kCrossSite,
41 };
42
GetSecFetchSiteHeaderString(const SecFetchSiteValue & value)43 const char* GetSecFetchSiteHeaderString(const SecFetchSiteValue& value) {
44 switch (value) {
45 case SecFetchSiteValue::kNoOrigin:
46 return "none";
47 case SecFetchSiteValue::kSameOrigin:
48 return "same-origin";
49 case SecFetchSiteValue::kSameSite:
50 return "same-site";
51 case SecFetchSiteValue::kCrossSite:
52 return "cross-site";
53 }
54 }
55
SecFetchSiteHeaderValue(const GURL & target_url,const url::Origin & initiator)56 SecFetchSiteValue SecFetchSiteHeaderValue(const GURL& target_url,
57 const url::Origin& initiator) {
58 url::Origin target_origin = url::Origin::Create(target_url);
59
60 if (target_origin == initiator)
61 return SecFetchSiteValue::kSameOrigin;
62
63 // Cross-scheme initiator should be considered cross-site (even if it's host
64 // is same-site with the target). See also https://crbug.com/979257.
65 if (initiator.scheme() == target_origin.scheme() &&
66 net::registry_controlled_domains::SameDomainOrHost(
67 initiator, target_origin,
68 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
69 return SecFetchSiteValue::kSameSite;
70 }
71
72 return SecFetchSiteValue::kCrossSite;
73 }
74
SetSecFetchSiteHeader(net::URLRequest * request,const GURL * pending_redirect_url,const mojom::URLLoaderFactoryParams & factory_params)75 void SetSecFetchSiteHeader(
76 net::URLRequest* request,
77 const GURL* pending_redirect_url,
78 const mojom::URLLoaderFactoryParams& factory_params) {
79 SecFetchSiteValue header_value;
80 url::Origin initiator = GetTrustworthyInitiator(
81 factory_params.request_initiator_site_lock, request->initiator());
82
83 // Browser-initiated requests with no initiator origin, and
84 // privileged requests initiated from a "non-webby" context will send
85 // `Sec-Fetch-Site: None` while unprivileged ones will send
86 // `Sec-Fetch-Site: cross-site`. Other requests default to `kSameOrigin`,
87 // and walk through the request's URL chain to calculate the
88 // correct value.
89 if (factory_params.unsafe_non_webby_initiator) {
90 cors::OriginAccessList origin_access_list;
91 origin_access_list.SetAllowListForOrigin(
92 factory_params.factory_bound_access_patterns->source_origin,
93 factory_params.factory_bound_access_patterns->allow_patterns);
94 if (origin_access_list.CheckAccessState(
95 factory_params.factory_bound_access_patterns->source_origin,
96 request->url()) == cors::OriginAccessList::AccessState::kAllowed) {
97 header_value = SecFetchSiteValue::kNoOrigin;
98 } else {
99 header_value = SecFetchSiteValue::kCrossSite;
100 }
101 } else if (factory_params.process_id == mojom::kBrowserProcessId &&
102 !request->initiator().has_value()) {
103 header_value = SecFetchSiteValue::kNoOrigin;
104 } else {
105 header_value = SecFetchSiteValue::kSameOrigin;
106 for (const GURL& target_url : request->url_chain()) {
107 header_value = std::max(header_value,
108 SecFetchSiteHeaderValue(target_url, initiator));
109 }
110 if (pending_redirect_url) {
111 header_value =
112 std::max(header_value,
113 SecFetchSiteHeaderValue(*pending_redirect_url, initiator));
114 }
115 }
116
117 request->SetExtraRequestHeaderByName(
118 kSecFetchSite, GetSecFetchSiteHeaderString(header_value),
119 /* overwrite = */ true);
120 }
121
122 // Sec-Fetch-Mode
SetSecFetchModeHeader(net::URLRequest * request,network::mojom::RequestMode mode)123 void SetSecFetchModeHeader(net::URLRequest* request,
124 network::mojom::RequestMode mode) {
125 std::string header_value = RequestModeToString(mode);
126
127 request->SetExtraRequestHeaderByName(kSecFetchMode, header_value, false);
128 }
129
130 // Sec-Fetch-User
SetSecFetchUserHeader(net::URLRequest * request,bool has_user_activation)131 void SetSecFetchUserHeader(net::URLRequest* request, bool has_user_activation) {
132 if (has_user_activation)
133 request->SetExtraRequestHeaderByName(kSecFetchUser, "?1", true);
134 else
135 request->RemoveRequestHeaderByName(kSecFetchUser);
136 }
137
138 // Sec-Fetch-Dest
SetSecFetchDestHeader(net::URLRequest * request,network::mojom::RequestDestination dest)139 void SetSecFetchDestHeader(net::URLRequest* request,
140 network::mojom::RequestDestination dest) {
141 std::string header_value = RequestDestinationToString(dest);
142 request->SetExtraRequestHeaderByName(kSecFetchDest, header_value, true);
143 }
144
145 } // namespace
146
SetFetchMetadataHeaders(net::URLRequest * request,network::mojom::RequestMode mode,bool has_user_activation,network::mojom::RequestDestination dest,const GURL * pending_redirect_url,const mojom::URLLoaderFactoryParams & factory_params)147 void SetFetchMetadataHeaders(
148 net::URLRequest* request,
149 network::mojom::RequestMode mode,
150 bool has_user_activation,
151 network::mojom::RequestDestination dest,
152 const GURL* pending_redirect_url,
153 const mojom::URLLoaderFactoryParams& factory_params) {
154 DCHECK(request);
155 DCHECK_NE(0u, request->url_chain().size());
156
157 // Only append the header to potentially trustworthy URLs.
158 const GURL& target_url =
159 pending_redirect_url ? *pending_redirect_url : request->url();
160 if (!IsUrlPotentiallyTrustworthy(target_url))
161 return;
162
163 SetSecFetchSiteHeader(request, pending_redirect_url, factory_params);
164 SetSecFetchModeHeader(request, mode);
165 SetSecFetchUserHeader(request, has_user_activation);
166 SetSecFetchDestHeader(request, dest);
167 }
168
MaybeRemoveSecHeaders(net::URLRequest * request,const GURL & pending_redirect_url)169 void MaybeRemoveSecHeaders(net::URLRequest* request,
170 const GURL& pending_redirect_url) {
171 DCHECK(request);
172
173 // If our redirect destination is not trusted it would not have had sec-ch- or
174 // sec-fetch- prefixed headers added to it. Our previous hops may have added
175 // these headers if the current url is trustworthy though so we should try to
176 // remove these now.
177 if (IsUrlPotentiallyTrustworthy(request->url()) &&
178 !IsUrlPotentiallyTrustworthy(pending_redirect_url)) {
179 // Check each of our request headers and if it is a "sec-ch-" or
180 // "sec-fetch-" prefixed header we'll remove it.
181 const net::HttpRequestHeaders::HeaderVector request_headers =
182 request->extra_request_headers().GetHeaderVector();
183 for (const auto& header : request_headers) {
184 if (StartsWith(header.key, "sec-ch-",
185 base::CompareCase::INSENSITIVE_ASCII) ||
186 StartsWith(header.key, "sec-fetch-",
187 base::CompareCase::INSENSITIVE_ASCII)) {
188 request->RemoveRequestHeaderByName(header.key);
189 }
190 }
191 }
192 }
193
194 } // namespace network
195