blob: f692760e7a8bdf35983f4f98e8a4166b2349c108 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/web_package/signed_exchange_utils.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/no_destructor.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/loader/download_utils_impl.h"
#include "content/browser/web_package/signed_exchange_devtools_proxy.h"
#include "content/browser/web_package/signed_exchange_error.h"
#include "content/browser/web_package/signed_exchange_request_handler.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/resource_response.h"
namespace content {
namespace signed_exchange_utils {
namespace {
base::Optional<base::Time> g_verification_time_for_testing;
} // namespace
void ReportErrorAndTraceEvent(
SignedExchangeDevToolsProxy* devtools_proxy,
const std::string& error_message,
base::Optional<SignedExchangeError::FieldIndexPair> error_field) {
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeError", TRACE_EVENT_SCOPE_THREAD, "error",
error_message);
if (devtools_proxy)
devtools_proxy->ReportError(error_message, std::move(error_field));
}
bool IsSignedExchangeHandlingEnabled(BrowserContext* context) {
if (!GetContentClient()->browser()->AllowSignedExchange(context))
return false;
return base::FeatureList::IsEnabled(features::kSignedHTTPExchange) ||
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExperimentalWebPlatformFeatures);
}
bool IsSignedExchangeReportingForDistributorsEnabled() {
return base::FeatureList::IsEnabled(network::features::kReporting) &&
(base::FeatureList::IsEnabled(
features::kSignedExchangeReportingForDistributors) ||
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExperimentalWebPlatformFeatures));
}
bool ShouldHandleAsSignedHTTPExchange(
const GURL& request_url,
const network::ResourceResponseHead& head) {
// Currently we don't support the signed exchange which is returned from a
// service worker.
// TODO(crbug/803774): Decide whether we should support it or not.
if (head.was_fetched_via_service_worker)
return false;
if (!SignedExchangeRequestHandler::IsSupportedMimeType(head.mime_type))
return false;
// Do not handle responses without HttpResponseHeaders.
// (Example: data:application/signed-exchange,)
if (!head.headers.get())
return false;
if (download_utils::MustDownload(request_url, head.headers.get(),
head.mime_type)) {
return false;
}
return true;
}
base::Optional<SignedExchangeVersion> GetSignedExchangeVersion(
const std::string& content_type) {
// https://wicg.github.io/webpackage/loading.html#signed-exchange-version
// Step 1. Let mimeType be the supplied MIME type of response. [spec text]
// |content_type| is the supplied MIME type.
// Step 2. If mimeType is undefined, return undefined. [spec text]
// Step 3. If mimeType's essence is not "application/signed-exchange", return
// undefined. [spec text]
const std::string::size_type semicolon = content_type.find(';');
const std::string essence = base::ToLowerASCII(base::TrimWhitespaceASCII(
content_type.substr(0, semicolon), base::TRIM_ALL));
if (essence != "application/signed-exchange")
return base::nullopt;
// Step 4.Let params be mimeType's parameters. [spec text]
std::map<std::string, std::string> params;
if (semicolon != base::StringPiece::npos) {
net::HttpUtil::NameValuePairsIterator parser(
content_type.begin() + semicolon + 1, content_type.end(), ';');
while (parser.GetNext()) {
const base::StringPiece name = parser.name_piece();
params[base::ToLowerASCII(name)] = parser.value();
}
if (!parser.valid())
return base::nullopt;
}
// Step 5. If params["v"] exists, return it. Otherwise, return undefined.
// [spec text]
auto iter = params.find("v");
if (iter != params.end()) {
if (iter->second == "b3")
return base::make_optional(SignedExchangeVersion::kB3);
return base::make_optional(SignedExchangeVersion::kUnknown);
}
return base::nullopt;
}
SignedExchangeLoadResult GetLoadResultFromSignatureVerifierResult(
SignedExchangeSignatureVerifier::Result verify_result) {
switch (verify_result) {
case SignedExchangeSignatureVerifier::Result::kSuccess:
return SignedExchangeLoadResult::kSuccess;
case SignedExchangeSignatureVerifier::Result::kErrCertificateSHA256Mismatch:
// "Handling the certificate reference
// ...
// - If the SHA-256 hash of chain’s leaf's certificate is not equal to
// certSha256, return "signature_verification_error"." [spec text]
return SignedExchangeLoadResult::kSignatureVerificationError;
case SignedExchangeSignatureVerifier::Result::
kErrSignatureVerificationFailed:
// "Validating a signature
// ...
// - If parsedSignature’s signature is not a valid signature of message
// by publicKey using the ecdsa_secp256r1_sha256 algorithm, return
// invalid." [spec text]
//
// "Parsing signed exchanges
// - ...
// - If parsedSignature is not valid for headerBytes and
// requestUrlBytes, and signed exchange version version, return
// "signature_verification_error"." [spec text]
return SignedExchangeLoadResult::kSignatureVerificationError;
case SignedExchangeSignatureVerifier::Result::kErrUnsupportedCertType:
// "Validating a signature
// ...
// - If parsedSignature’s signature is not a valid signature of message
// by publicKey using the ecdsa_secp256r1_sha256 algorithm, return
// invalid." [spec text]
//
// "Parsing signed exchanges
// - ...
// - If parsedSignature is not valid for headerBytes and
// requestUrlBytes, and signed exchange version version, return
// "signature_verification_error"." [spec text]
return SignedExchangeLoadResult::kSignatureVerificationError;
case SignedExchangeSignatureVerifier::Result::kErrValidityPeriodTooLong:
// "Cross-origin trust
// ...
// - If signature’s expiration time is more than 604800 seconds (7 days)
// after signature’s date, return "untrusted"." [spec text]
//
// "Parsing signed exchanges
// - ...
// - If parsedSignature does not establish cross-origin trust for
// parsedExchange, return "cert_verification_error"." [spec text]
return SignedExchangeLoadResult::kCertVerificationError;
case SignedExchangeSignatureVerifier::Result::kErrFutureDate:
case SignedExchangeSignatureVerifier::Result::kErrExpired:
// "Validating a signature
// ...
// - If the UA’s estimate of the current time is more than clockSkew
// before signature’s date, return "untrusted".
// - If the UA’s estimate of the current time is after signature’s
// expiration time, return "untrusted"." [spec text]
//
// "Parsing signed exchanges
// - ...
// - If parsedSignature is not valid for headerBytes and
// requestUrlBytes, and signed exchange version version, return
// "signature_verification_error"." [spec text]
return SignedExchangeLoadResult::kSignatureVerificationError;
// Deprecated error results.
case SignedExchangeSignatureVerifier::Result::kErrNoCertificate_deprecated:
case SignedExchangeSignatureVerifier::Result::
kErrNoCertificateSHA256_deprecated:
case SignedExchangeSignatureVerifier::Result::
kErrInvalidSignatureFormat_deprecated:
case SignedExchangeSignatureVerifier::Result::
kErrInvalidSignatureIntegrity_deprecated:
case SignedExchangeSignatureVerifier::Result::
kErrInvalidTimestamp_deprecated:
NOTREACHED();
return SignedExchangeLoadResult::kSignatureVerificationError;
}
NOTREACHED();
return SignedExchangeLoadResult::kSignatureVerificationError;
}
net::RedirectInfo CreateRedirectInfo(
const GURL& new_url,
const network::ResourceRequest& outer_request,
const network::ResourceResponseHead& outer_response,
bool is_fallback_redirect) {
// https://wicg.github.io/webpackage/loading.html#mp-http-fetch
// Step 3. Set actualResponse's status to 303. [spec text]
return net::RedirectInfo::ComputeRedirectInfo(
"GET", outer_request.url, outer_request.request_initiator,
outer_request.site_for_cookies,
outer_request.update_first_party_url_on_redirect
? net::URLRequest::FirstPartyURLPolicy::
UPDATE_FIRST_PARTY_URL_ON_REDIRECT
: net::URLRequest::FirstPartyURLPolicy::NEVER_CHANGE_FIRST_PARTY_URL,
outer_request.referrer_policy, outer_request.referrer.spec(), 303,
new_url,
net::RedirectUtil::GetReferrerPolicyHeader(outer_response.headers.get()),
false /* insecure_scheme_was_upgraded */, true /* copy_fragment */,
is_fallback_redirect);
}
network::ResourceResponseHead CreateRedirectResponseHead(
const network::ResourceResponseHead& outer_response,
bool is_fallback_redirect) {
network::ResourceResponseHead response_head;
response_head.encoded_data_length = 0;
std::string buf;
std::string link_header;
if (!is_fallback_redirect &&
outer_response.headers) {
outer_response.headers->GetNormalizedHeader("link", &link_header);
}
if (link_header.empty()) {
buf = base::StringPrintf("HTTP/1.1 %d %s\r\n", 303, "See Other");
} else {
buf = base::StringPrintf(
"HTTP/1.1 %d %s\r\n"
"link: %s\r\n",
303, "See Other", link_header.c_str());
}
response_head.headers = base::MakeRefCounted<net::HttpResponseHeaders>(
net::HttpUtil::AssembleRawHeaders(buf));
response_head.encoded_data_length = 0;
response_head.request_start = outer_response.request_start;
response_head.response_start = outer_response.response_start;
response_head.request_time = outer_response.request_time;
response_head.response_time = outer_response.response_time;
response_head.load_timing = outer_response.load_timing;
return response_head;
}
int MakeRequestID() {
// Request ID for browser initiated requests. request_ids generated by
// child processes are counted up from 0, while browser created requests
// start at -2 and go down from there. (We need to start at -2 because -1 is
// used as a special value all over the resource_dispatcher_host for
// uninitialized variables.) This way, we no longer have the unlikely (but
// observed in the real world!) event where we have two requests with the same
// request_id_.
static base::NoDestructor<std::atomic_int> request_id(-1);
return --*request_id;
}
base::Time GetVerificationTime() {
if (g_verification_time_for_testing)
return *g_verification_time_for_testing;
return base::Time::Now();
}
void SetVerificationTimeForTesting(
base::Optional<base::Time> verification_time_for_testing) {
g_verification_time_for_testing = verification_time_for_testing;
}
} // namespace signed_exchange_utils
} // namespace content