Tsuyoshi Horo | cdbb490 | 2018-04-12 06:09:14 | [diff] [blame] | 1 | // 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 Sakamoto | 0cc9371 | 2019-01-11 06:26:53 | [diff] [blame] | 7 | #include "base/command_line.h" |
Tsuyoshi Horo | 46f5fff | 2018-05-10 12:33:35 | [diff] [blame] | 8 | #include "base/feature_list.h" |
John Abd-El-Malek | d96edf3 | 2019-07-29 22:04:52 | [diff] [blame] | 9 | #include "base/no_destructor.h" |
Kouhei Ueno | 17843f4 | 2018-09-27 02:44:55 | [diff] [blame] | 10 | #include "base/strings/string_piece.h" |
Kunihiko Sakamoto | b5c94d90 | 2018-09-04 04:09:02 | [diff] [blame] | 11 | #include "base/strings/string_util.h" |
Hans Wennborg | 5ffd139 | 2019-10-16 11:00:02 | [diff] [blame^] | 12 | #include "base/strings/stringprintf.h" |
Tsuyoshi Horo | 46f5fff | 2018-05-10 12:33:35 | [diff] [blame] | 13 | #include "base/time/time.h" |
Tsuyoshi Horo | cdbb490 | 2018-04-12 06:09:14 | [diff] [blame] | 14 | #include "base/trace_event/trace_event.h" |
Tsuyoshi Horo | 154dafc | 2018-11-06 09:32:28 | [diff] [blame] | 15 | #include "content/browser/loader/download_utils_impl.h" |
Tsuyoshi Horo | 4801e76 | 2018-04-25 07:36:57 | [diff] [blame] | 16 | #include "content/browser/web_package/signed_exchange_devtools_proxy.h" |
Tsuyoshi Horo | b40c7c3 | 2018-05-31 07:32:45 | [diff] [blame] | 17 | #include "content/browser/web_package/signed_exchange_error.h" |
Kunihiko Sakamoto | e6aa22e | 2018-06-15 03:26:55 | [diff] [blame] | 18 | #include "content/browser/web_package/signed_exchange_request_handler.h" |
Kunihiko Sakamoto | f586da6 | 2019-03-28 03:03:04 | [diff] [blame] | 19 | #include "content/public/browser/content_browser_client.h" |
| 20 | #include "content/public/common/content_client.h" |
Tsuyoshi Horo | 46f5fff | 2018-05-10 12:33:35 | [diff] [blame] | 21 | #include "content/public/common/content_features.h" |
Kunihiko Sakamoto | 0cc9371 | 2019-01-11 06:26:53 | [diff] [blame] | 22 | #include "content/public/common/content_switches.h" |
Kunihiko Sakamoto | b5c94d90 | 2018-09-04 04:09:02 | [diff] [blame] | 23 | #include "net/http/http_util.h" |
Tsuyoshi Horo | 4f5ce901 | 2019-02-27 01:04:45 | [diff] [blame] | 24 | #include "services/network/public/cpp/features.h" |
Tsuyoshi Horo | 46f5fff | 2018-05-10 12:33:35 | [diff] [blame] | 25 | #include "services/network/public/cpp/resource_response.h" |
Tsuyoshi Horo | cdbb490 | 2018-04-12 06:09:14 | [diff] [blame] | 26 | |
| 27 | namespace content { |
| 28 | namespace signed_exchange_utils { |
| 29 | |
Tsuyoshi Horo | af905953 | 2019-08-29 15:27:02 | [diff] [blame] | 30 | namespace { |
| 31 | base::Optional<base::Time> g_verification_time_for_testing; |
| 32 | } // namespace |
| 33 | |
Tsuyoshi Horo | 6361cb0 | 2018-06-04 04:36:02 | [diff] [blame] | 34 | void ReportErrorAndTraceEvent( |
Tsuyoshi Horo | b40c7c3 | 2018-05-31 07:32:45 | [diff] [blame] | 35 | SignedExchangeDevToolsProxy* devtools_proxy, |
Tsuyoshi Horo | b40c7c3 | 2018-05-31 07:32:45 | [diff] [blame] | 36 | const std::string& error_message, |
| 37 | base::Optional<SignedExchangeError::FieldIndexPair> error_field) { |
Tsuyoshi Horo | 6361cb0 | 2018-06-04 04:36:02 | [diff] [blame] | 38 | TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("loading"), |
| 39 | "SignedExchangeError", TRACE_EVENT_SCOPE_THREAD, "error", |
| 40 | error_message); |
Tsuyoshi Horo | 4801e76 | 2018-04-25 07:36:57 | [diff] [blame] | 41 | if (devtools_proxy) |
Tsuyoshi Horo | b40c7c3 | 2018-05-31 07:32:45 | [diff] [blame] | 42 | devtools_proxy->ReportError(error_message, std::move(error_field)); |
Tsuyoshi Horo | cdbb490 | 2018-04-12 06:09:14 | [diff] [blame] | 43 | } |
| 44 | |
Clark DuVall | ab63d14 | 2019-07-23 04:24:36 | [diff] [blame] | 45 | bool IsSignedExchangeHandlingEnabled(BrowserContext* context) { |
Kunihiko Sakamoto | f586da6 | 2019-03-28 03:03:04 | [diff] [blame] | 46 | if (!GetContentClient()->browser()->AllowSignedExchange(context)) |
| 47 | return false; |
| 48 | |
Kunihiko Sakamoto | 0cc9371 | 2019-01-11 06:26:53 | [diff] [blame] | 49 | return base::FeatureList::IsEnabled(features::kSignedHTTPExchange) || |
| 50 | base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 51 | switches::kEnableExperimentalWebPlatformFeatures); |
| 52 | } |
| 53 | |
Tsuyoshi Horo | 4f5ce901 | 2019-02-27 01:04:45 | [diff] [blame] | 54 | bool 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 Horo | 46f5fff | 2018-05-10 12:33:35 | [diff] [blame] | 62 | bool 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 Sakamoto | e6aa22e | 2018-06-15 03:26:55 | [diff] [blame] | 70 | if (!SignedExchangeRequestHandler::IsSupportedMimeType(head.mime_type)) |
Tsuyoshi Horo | 46f5fff | 2018-05-10 12:33:35 | [diff] [blame] | 71 | return false; |
Tsuyoshi Horo | 73af162 | 2019-02-28 05:34:04 | [diff] [blame] | 72 | // Do not handle responses without HttpResponseHeaders. |
| 73 | // (Example: data:application/signed-exchange,) |
| 74 | if (!head.headers.get()) |
| 75 | return false; |
Tsuyoshi Horo | 154dafc | 2018-11-06 09:32:28 | [diff] [blame] | 76 | if (download_utils::MustDownload(request_url, head.headers.get(), |
| 77 | head.mime_type)) { |
| 78 | return false; |
| 79 | } |
Kunihiko Sakamoto | f586da6 | 2019-03-28 03:03:04 | [diff] [blame] | 80 | return true; |
Tsuyoshi Horo | 46f5fff | 2018-05-10 12:33:35 | [diff] [blame] | 81 | } |
| 82 | |
Kunihiko Sakamoto | b5c94d90 | 2018-09-04 04:09:02 | [diff] [blame] | 83 | base::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 Benjamin | e4b880e | 2019-04-26 00:07:51 | [diff] [blame] | 103 | const base::StringPiece name = parser.name_piece(); |
Kunihiko Sakamoto | b5c94d90 | 2018-09-04 04:09:02 | [diff] [blame] | 104 | 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 Ueno | 9007cf2 | 2019-01-09 08:23:24 | [diff] [blame] | 113 | if (iter->second == "b3") |
| 114 | return base::make_optional(SignedExchangeVersion::kB3); |
Kunihiko Sakamoto | b5c94d90 | 2018-09-04 04:09:02 | [diff] [blame] | 115 | return base::make_optional(SignedExchangeVersion::kUnknown); |
| 116 | } |
| 117 | return base::nullopt; |
| 118 | } |
| 119 | |
Tsuyoshi Horo | 06eb28f | 2019-02-21 13:52:24 | [diff] [blame] | 120 | SignedExchangeLoadResult 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 Horo | 06eb28f | 2019-02-21 13:52:24 | [diff] [blame] | 145 | 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 Sakamoto | 44cd9a8 | 2019-02-26 05:47:06 | [diff] [blame] | 192 | kErrInvalidSignatureIntegrity_deprecated: |
| 193 | case SignedExchangeSignatureVerifier::Result:: |
Tsuyoshi Horo | 06eb28f | 2019-02-21 13:52:24 | [diff] [blame] | 194 | kErrInvalidTimestamp_deprecated: |
| 195 | NOTREACHED(); |
| 196 | return SignedExchangeLoadResult::kSignatureVerificationError; |
| 197 | } |
| 198 | |
| 199 | NOTREACHED(); |
| 200 | return SignedExchangeLoadResult::kSignatureVerificationError; |
| 201 | } |
| 202 | |
Tsuyoshi Horo | d5eb761 | 2019-05-09 08:59:46 | [diff] [blame] | 203 | net::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 Farolino | bda4c12 | 2019-09-16 16:03:30 | [diff] [blame] | 211 | "GET", outer_request.url, outer_request.site_for_cookies, |
Tsuyoshi Horo | d5eb761 | 2019-05-09 08:59:46 | [diff] [blame] | 212 | 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 | |
| 223 | network::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 Horo | d5eb761 | 2019-05-09 08:59:46 | [diff] [blame] | 231 | 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 Horo | d5eb761 | 2019-05-09 08:59:46 | [diff] [blame] | 237 | 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-Malek | d96edf3 | 2019-07-29 22:04:52 | [diff] [blame] | 253 | int 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 Horo | af905953 | 2019-08-29 15:27:02 | [diff] [blame] | 266 | base::Time GetVerificationTime() { |
| 267 | if (g_verification_time_for_testing) |
| 268 | return *g_verification_time_for_testing; |
| 269 | return base::Time::Now(); |
| 270 | } |
| 271 | |
| 272 | void SetVerificationTimeForTesting( |
| 273 | base::Optional<base::Time> verification_time_for_testing) { |
| 274 | g_verification_time_for_testing = verification_time_for_testing; |
| 275 | } |
| 276 | |
Tsuyoshi Horo | cdbb490 | 2018-04-12 06:09:14 | [diff] [blame] | 277 | } // namespace signed_exchange_utils |
| 278 | } // namespace content |