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