blob: d01b3ea345b8c787e151ddf8ebdbf33010843d04 [file] [log] [blame]
Tsuyoshi Horocdbb4902018-04-12 06:09:141// Copyright 2018 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 "content/browser/web_package/signed_exchange_utils.h"
6
Kunihiko Sakamoto0cc93712019-01-11 06:26:537#include "base/command_line.h"
Tsuyoshi Horo46f5fff2018-05-10 12:33:358#include "base/feature_list.h"
John Abd-El-Malekd96edf32019-07-29 22:04:529#include "base/no_destructor.h"
Kouhei Ueno17843f42018-09-27 02:44:5510#include "base/strings/string_piece.h"
Kunihiko Sakamotob5c94d902018-09-04 04:09:0211#include "base/strings/string_util.h"
Hans Wennborg5ffd1392019-10-16 11:00:0212#include "base/strings/stringprintf.h"
Tsuyoshi Horo46f5fff2018-05-10 12:33:3513#include "base/time/time.h"
Tsuyoshi Horocdbb4902018-04-12 06:09:1414#include "base/trace_event/trace_event.h"
Tsuyoshi Horo154dafc2018-11-06 09:32:2815#include "content/browser/loader/download_utils_impl.h"
Tsuyoshi Horo4801e762018-04-25 07:36:5716#include "content/browser/web_package/signed_exchange_devtools_proxy.h"
Tsuyoshi Horob40c7c32018-05-31 07:32:4517#include "content/browser/web_package/signed_exchange_error.h"
Kunihiko Sakamotoe6aa22e2018-06-15 03:26:5518#include "content/browser/web_package/signed_exchange_request_handler.h"
Kunihiko Sakamotof586da62019-03-28 03:03:0419#include "content/public/browser/content_browser_client.h"
20#include "content/public/common/content_client.h"
Tsuyoshi Horo46f5fff2018-05-10 12:33:3521#include "content/public/common/content_features.h"
Kunihiko Sakamoto0cc93712019-01-11 06:26:5322#include "content/public/common/content_switches.h"
Kunihiko Sakamotob5c94d902018-09-04 04:09:0223#include "net/http/http_util.h"
Tsuyoshi Horo4f5ce9012019-02-27 01:04:4524#include "services/network/public/cpp/features.h"
Tsuyoshi Horo46f5fff2018-05-10 12:33:3525#include "services/network/public/cpp/resource_response.h"
Tsuyoshi Horocdbb4902018-04-12 06:09:1426
27namespace content {
28namespace signed_exchange_utils {
29
Tsuyoshi Horoaf9059532019-08-29 15:27:0230namespace {
31base::Optional<base::Time> g_verification_time_for_testing;
32} // namespace
33
Tsuyoshi Horo6361cb02018-06-04 04:36:0234void ReportErrorAndTraceEvent(
Tsuyoshi Horob40c7c32018-05-31 07:32:4535 SignedExchangeDevToolsProxy* devtools_proxy,
Tsuyoshi Horob40c7c32018-05-31 07:32:4536 const std::string& error_message,
37 base::Optional<SignedExchangeError::FieldIndexPair> error_field) {
Tsuyoshi Horo6361cb02018-06-04 04:36:0238 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("loading"),
39 "SignedExchangeError", TRACE_EVENT_SCOPE_THREAD, "error",
40 error_message);
Tsuyoshi Horo4801e762018-04-25 07:36:5741 if (devtools_proxy)
Tsuyoshi Horob40c7c32018-05-31 07:32:4542 devtools_proxy->ReportError(error_message, std::move(error_field));
Tsuyoshi Horocdbb4902018-04-12 06:09:1443}
44
Clark DuVallab63d142019-07-23 04:24:3645bool IsSignedExchangeHandlingEnabled(BrowserContext* context) {
Kunihiko Sakamotof586da62019-03-28 03:03:0446 if (!GetContentClient()->browser()->AllowSignedExchange(context))
47 return false;
48
Kunihiko Sakamoto0cc93712019-01-11 06:26:5349 return base::FeatureList::IsEnabled(features::kSignedHTTPExchange) ||
50 base::CommandLine::ForCurrentProcess()->HasSwitch(
51 switches::kEnableExperimentalWebPlatformFeatures);
52}
53
Tsuyoshi Horo4f5ce9012019-02-27 01:04:4554bool IsSignedExchangeReportingForDistributorsEnabled() {
55 return base::FeatureList::IsEnabled(network::features::kReporting) &&
56 (base::FeatureList::IsEnabled(
57 features::kSignedExchangeReportingForDistributors) ||
58 base::CommandLine::ForCurrentProcess()->HasSwitch(
59 switches::kEnableExperimentalWebPlatformFeatures));
60}
61
Tsuyoshi Horo46f5fff2018-05-10 12:33:3562bool ShouldHandleAsSignedHTTPExchange(
63 const GURL& request_url,
64 const network::ResourceResponseHead& head) {
65 // Currently we don't support the signed exchange which is returned from a
66 // service worker.
67 // TODO(crbug/803774): Decide whether we should support it or not.
68 if (head.was_fetched_via_service_worker)
69 return false;
Kunihiko Sakamotoe6aa22e2018-06-15 03:26:5570 if (!SignedExchangeRequestHandler::IsSupportedMimeType(head.mime_type))
Tsuyoshi Horo46f5fff2018-05-10 12:33:3571 return false;
Tsuyoshi Horo73af1622019-02-28 05:34:0472 // Do not handle responses without HttpResponseHeaders.
73 // (Example: data:application/signed-exchange,)
74 if (!head.headers.get())
75 return false;
Tsuyoshi Horo154dafc2018-11-06 09:32:2876 if (download_utils::MustDownload(request_url, head.headers.get(),
77 head.mime_type)) {
78 return false;
79 }
Kunihiko Sakamotof586da62019-03-28 03:03:0480 return true;
Tsuyoshi Horo46f5fff2018-05-10 12:33:3581}
82
Kunihiko Sakamotob5c94d902018-09-04 04:09:0283base::Optional<SignedExchangeVersion> GetSignedExchangeVersion(
84 const std::string& content_type) {
85 // https://wicg.github.io/webpackage/loading.html#signed-exchange-version
86 // Step 1. Let mimeType be the supplied MIME type of response. [spec text]
87 // |content_type| is the supplied MIME type.
88 // Step 2. If mimeType is undefined, return undefined. [spec text]
89 // Step 3. If mimeType's essence is not "application/signed-exchange", return
90 // undefined. [spec text]
91 const std::string::size_type semicolon = content_type.find(';');
92 const std::string essence = base::ToLowerASCII(base::TrimWhitespaceASCII(
93 content_type.substr(0, semicolon), base::TRIM_ALL));
94 if (essence != "application/signed-exchange")
95 return base::nullopt;
96
97 // Step 4.Let params be mimeType's parameters. [spec text]
98 std::map<std::string, std::string> params;
99 if (semicolon != base::StringPiece::npos) {
100 net::HttpUtil::NameValuePairsIterator parser(
101 content_type.begin() + semicolon + 1, content_type.end(), ';');
102 while (parser.GetNext()) {
David Benjamine4b880e2019-04-26 00:07:51103 const base::StringPiece name = parser.name_piece();
Kunihiko Sakamotob5c94d902018-09-04 04:09:02104 params[base::ToLowerASCII(name)] = parser.value();
105 }
106 if (!parser.valid())
107 return base::nullopt;
108 }
109 // Step 5. If params["v"] exists, return it. Otherwise, return undefined.
110 // [spec text]
111 auto iter = params.find("v");
112 if (iter != params.end()) {
Kouhei Ueno9007cf22019-01-09 08:23:24113 if (iter->second == "b3")
114 return base::make_optional(SignedExchangeVersion::kB3);
Kunihiko Sakamotob5c94d902018-09-04 04:09:02115 return base::make_optional(SignedExchangeVersion::kUnknown);
116 }
117 return base::nullopt;
118}
119
Tsuyoshi Horo06eb28f2019-02-21 13:52:24120SignedExchangeLoadResult GetLoadResultFromSignatureVerifierResult(
121 SignedExchangeSignatureVerifier::Result verify_result) {
122 switch (verify_result) {
123 case SignedExchangeSignatureVerifier::Result::kSuccess:
124 return SignedExchangeLoadResult::kSuccess;
125 case SignedExchangeSignatureVerifier::Result::kErrCertificateSHA256Mismatch:
126 // "Handling the certificate reference
127 // ...
128 // - If the SHA-256 hash of chain’s leaf's certificate is not equal to
129 // certSha256, return "signature_verification_error"." [spec text]
130 return SignedExchangeLoadResult::kSignatureVerificationError;
131 case SignedExchangeSignatureVerifier::Result::
132 kErrSignatureVerificationFailed:
133 // "Validating a signature
134 // ...
135 // - If parsedSignature’s signature is not a valid signature of message
136 // by publicKey using the ecdsa_secp256r1_sha256 algorithm, return
137 // invalid." [spec text]
138 //
139 // "Parsing signed exchanges
140 // - ...
141 // - If parsedSignature is not valid for headerBytes and
142 // requestUrlBytes, and signed exchange version version, return
143 // "signature_verification_error"." [spec text]
144 return SignedExchangeLoadResult::kSignatureVerificationError;
Tsuyoshi Horo06eb28f2019-02-21 13:52:24145 case SignedExchangeSignatureVerifier::Result::kErrUnsupportedCertType:
146 // "Validating a signature
147 // ...
148 // - If parsedSignature’s signature is not a valid signature of message
149 // by publicKey using the ecdsa_secp256r1_sha256 algorithm, return
150 // invalid." [spec text]
151 //
152 // "Parsing signed exchanges
153 // - ...
154 // - If parsedSignature is not valid for headerBytes and
155 // requestUrlBytes, and signed exchange version version, return
156 // "signature_verification_error"." [spec text]
157 return SignedExchangeLoadResult::kSignatureVerificationError;
158 case SignedExchangeSignatureVerifier::Result::kErrValidityPeriodTooLong:
159 // "Cross-origin trust
160 // ...
161 // - If signature’s expiration time is more than 604800 seconds (7 days)
162 // after signature’s date, return "untrusted"." [spec text]
163 //
164 // "Parsing signed exchanges
165 // - ...
166 // - If parsedSignature does not establish cross-origin trust for
167 // parsedExchange, return "cert_verification_error"." [spec text]
168 return SignedExchangeLoadResult::kCertVerificationError;
169 case SignedExchangeSignatureVerifier::Result::kErrFutureDate:
170 case SignedExchangeSignatureVerifier::Result::kErrExpired:
171 // "Validating a signature
172 // ...
173 // - If the UA’s estimate of the current time is more than clockSkew
174 // before signature’s date, return "untrusted".
175 // - If the UA’s estimate of the current time is after signature’s
176 // expiration time, return "untrusted"." [spec text]
177 //
178 // "Parsing signed exchanges
179 // - ...
180 // - If parsedSignature is not valid for headerBytes and
181 // requestUrlBytes, and signed exchange version version, return
182 // "signature_verification_error"." [spec text]
183 return SignedExchangeLoadResult::kSignatureVerificationError;
184
185 // Deprecated error results.
186 case SignedExchangeSignatureVerifier::Result::kErrNoCertificate_deprecated:
187 case SignedExchangeSignatureVerifier::Result::
188 kErrNoCertificateSHA256_deprecated:
189 case SignedExchangeSignatureVerifier::Result::
190 kErrInvalidSignatureFormat_deprecated:
191 case SignedExchangeSignatureVerifier::Result::
Kunihiko Sakamoto44cd9a82019-02-26 05:47:06192 kErrInvalidSignatureIntegrity_deprecated:
193 case SignedExchangeSignatureVerifier::Result::
Tsuyoshi Horo06eb28f2019-02-21 13:52:24194 kErrInvalidTimestamp_deprecated:
195 NOTREACHED();
196 return SignedExchangeLoadResult::kSignatureVerificationError;
197 }
198
199 NOTREACHED();
200 return SignedExchangeLoadResult::kSignatureVerificationError;
201}
202
Tsuyoshi Horod5eb7612019-05-09 08:59:46203net::RedirectInfo CreateRedirectInfo(
204 const GURL& new_url,
205 const network::ResourceRequest& outer_request,
206 const network::ResourceResponseHead& outer_response,
207 bool is_fallback_redirect) {
208 // https://wicg.github.io/webpackage/loading.html#mp-http-fetch
209 // Step 3. Set actualResponse's status to 303. [spec text]
210 return net::RedirectInfo::ComputeRedirectInfo(
Dominic Farolinobda4c122019-09-16 16:03:30211 "GET", outer_request.url, outer_request.site_for_cookies,
Tsuyoshi Horod5eb7612019-05-09 08:59:46212 outer_request.update_first_party_url_on_redirect
213 ? net::URLRequest::FirstPartyURLPolicy::
214 UPDATE_FIRST_PARTY_URL_ON_REDIRECT
215 : net::URLRequest::FirstPartyURLPolicy::NEVER_CHANGE_FIRST_PARTY_URL,
216 outer_request.referrer_policy, outer_request.referrer.spec(), 303,
217 new_url,
218 net::RedirectUtil::GetReferrerPolicyHeader(outer_response.headers.get()),
219 false /* insecure_scheme_was_upgraded */, true /* copy_fragment */,
220 is_fallback_redirect);
221}
222
223network::ResourceResponseHead CreateRedirectResponseHead(
224 const network::ResourceResponseHead& outer_response,
225 bool is_fallback_redirect) {
226 network::ResourceResponseHead response_head;
227 response_head.encoded_data_length = 0;
228 std::string buf;
229 std::string link_header;
230 if (!is_fallback_redirect &&
Tsuyoshi Horod5eb7612019-05-09 08:59:46231 outer_response.headers) {
232 outer_response.headers->GetNormalizedHeader("link", &link_header);
233 }
234 if (link_header.empty()) {
235 buf = base::StringPrintf("HTTP/1.1 %d %s\r\n", 303, "See Other");
236 } else {
Tsuyoshi Horod5eb7612019-05-09 08:59:46237 buf = base::StringPrintf(
238 "HTTP/1.1 %d %s\r\n"
239 "link: %s\r\n",
240 303, "See Other", link_header.c_str());
241 }
242 response_head.headers = base::MakeRefCounted<net::HttpResponseHeaders>(
243 net::HttpUtil::AssembleRawHeaders(buf));
244 response_head.encoded_data_length = 0;
245 response_head.request_start = outer_response.request_start;
246 response_head.response_start = outer_response.response_start;
247 response_head.request_time = outer_response.request_time;
248 response_head.response_time = outer_response.response_time;
249 response_head.load_timing = outer_response.load_timing;
250 return response_head;
251}
252
John Abd-El-Malekd96edf32019-07-29 22:04:52253int MakeRequestID() {
254 // Request ID for browser initiated requests. request_ids generated by
255 // child processes are counted up from 0, while browser created requests
256 // start at -2 and go down from there. (We need to start at -2 because -1 is
257 // used as a special value all over the resource_dispatcher_host for
258 // uninitialized variables.) This way, we no longer have the unlikely (but
259 // observed in the real world!) event where we have two requests with the same
260 // request_id_.
261 static base::NoDestructor<std::atomic_int> request_id(-1);
262
263 return --*request_id;
264}
265
Tsuyoshi Horoaf9059532019-08-29 15:27:02266base::Time GetVerificationTime() {
267 if (g_verification_time_for_testing)
268 return *g_verification_time_for_testing;
269 return base::Time::Now();
270}
271
272void SetVerificationTimeForTesting(
273 base::Optional<base::Time> verification_time_for_testing) {
274 g_verification_time_for_testing = verification_time_for_testing;
275}
276
Tsuyoshi Horocdbb4902018-04-12 06:09:14277} // namespace signed_exchange_utils
278} // namespace content