Commit 60459f54 authored by Kunihiko Sakamoto's avatar Kunihiko Sakamoto Committed by Commit Bot

WebBundles: Require nosniff header in webbundle responses

After this patch, Web Bundles response from network without
"X-Content-Type-Options: nosniff" header will cause
net::ERR_INVALID_WEB_BUNDLE error.

Spec: https://wicg.github.io/webpackage/draft-yasskin-wpack-bundled-exchanges.html#name-serving-constraints

Bug: 1018640
Change-Id: I32632ee547ab32ea6646d0150dcc9493c374c263
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1992944Reviewed-by: default avatarTsuyoshi Horo <horo@chromium.org>
Commit-Queue: Kunihiko Sakamoto <ksakamoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#731849}
parent 9671e688
...@@ -42,15 +42,6 @@ namespace { ...@@ -42,15 +42,6 @@ namespace {
constexpr char kLoadResultHistogram[] = "SignedExchange.LoadResult2"; constexpr char kLoadResultHistogram[] = "SignedExchange.LoadResult2";
constexpr char kPrefetchLoadResultHistogram[] = constexpr char kPrefetchLoadResultHistogram[] =
"SignedExchange.Prefetch.LoadResult2"; "SignedExchange.Prefetch.LoadResult2";
constexpr char kContentTypeOptionsHeaderName[] = "x-content-type-options";
constexpr char kNoSniffHeaderValue[] = "nosniff";
bool HasNoSniffHeader(const network::mojom::URLResponseHead& response) {
std::string content_type_options;
response.headers->EnumerateHeader(nullptr, kContentTypeOptionsHeaderName,
&content_type_options);
return base::LowerCaseEqualsASCII(content_type_options, kNoSniffHeaderValue);
}
SignedExchangeHandlerFactory* g_signed_exchange_factory_for_testing_ = nullptr; SignedExchangeHandlerFactory* g_signed_exchange_factory_for_testing_ = nullptr;
...@@ -163,7 +154,8 @@ void SignedExchangeLoader::OnStartLoadingResponseBody( ...@@ -163,7 +154,8 @@ void SignedExchangeLoader::OnStartLoadingResponseBody(
signed_exchange_handler_ = std::make_unique<SignedExchangeHandler>( signed_exchange_handler_ = std::make_unique<SignedExchangeHandler>(
IsOriginSecure(outer_request_.url), IsOriginSecure(outer_request_.url),
HasNoSniffHeader(*outer_response_head_), content_type_, signed_exchange_utils::HasNoSniffHeader(*outer_response_head_),
content_type_,
std::make_unique<network::DataPipeToSourceStream>( std::make_unique<network::DataPipeToSourceStream>(
std::move(response_body)), std::move(response_body)),
base::BindOnce(&SignedExchangeLoader::OnHTTPExchangeFound, base::BindOnce(&SignedExchangeLoader::OnHTTPExchangeFound,
......
...@@ -28,6 +28,8 @@ namespace content { ...@@ -28,6 +28,8 @@ namespace content {
namespace signed_exchange_utils { namespace signed_exchange_utils {
namespace { namespace {
constexpr char kContentTypeOptionsHeaderName[] = "x-content-type-options";
constexpr char kNoSniffHeaderValue[] = "nosniff";
base::Optional<base::Time> g_verification_time_for_testing; base::Optional<base::Time> g_verification_time_for_testing;
} // namespace } // namespace
...@@ -80,6 +82,13 @@ bool ShouldHandleAsSignedHTTPExchange( ...@@ -80,6 +82,13 @@ bool ShouldHandleAsSignedHTTPExchange(
return true; return true;
} }
bool HasNoSniffHeader(const network::mojom::URLResponseHead& response) {
std::string content_type_options;
response.headers->EnumerateHeader(nullptr, kContentTypeOptionsHeaderName,
&content_type_options);
return base::LowerCaseEqualsASCII(content_type_options, kNoSniffHeaderValue);
}
base::Optional<SignedExchangeVersion> GetSignedExchangeVersion( base::Optional<SignedExchangeVersion> GetSignedExchangeVersion(
const std::string& content_type) { const std::string& content_type) {
// https://wicg.github.io/webpackage/loading.html#signed-exchange-version // https://wicg.github.io/webpackage/loading.html#signed-exchange-version
......
...@@ -59,6 +59,9 @@ bool ShouldHandleAsSignedHTTPExchange( ...@@ -59,6 +59,9 @@ bool ShouldHandleAsSignedHTTPExchange(
const GURL& request_url, const GURL& request_url,
const network::mojom::URLResponseHead& head); const network::mojom::URLResponseHead& head);
// Returns true if |response| has "X-Content-Type-Options: nosniff" header.
bool HasNoSniffHeader(const network::mojom::URLResponseHead& response);
// Extracts the signed exchange version [1] from |content_type|, and converts it // Extracts the signed exchange version [1] from |content_type|, and converts it
// to SignedExchanveVersion. Returns nullopt if the mime type is not a variant // to SignedExchanveVersion. Returns nullopt if the mime type is not a variant
// of application/signed-exchange. Returns SignedExchangeVersion::kUnknown if an // of application/signed-exchange. Returns SignedExchangeVersion::kUnknown if an
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "base/task/post_task.h" #include "base/task/post_task.h"
#include "content/browser/loader/navigation_loader_interceptor.h" #include "content/browser/loader/navigation_loader_interceptor.h"
#include "content/browser/loader/single_request_url_loader_factory.h" #include "content/browser/loader/single_request_url_loader_factory.h"
#include "content/browser/web_package/signed_exchange_utils.h"
#include "content/browser/web_package/web_bundle_handle_tracker.h" #include "content/browser/web_package/web_bundle_handle_tracker.h"
#include "content/browser/web_package/web_bundle_navigation_info.h" #include "content/browser/web_package/web_bundle_navigation_info.h"
#include "content/browser/web_package/web_bundle_reader.h" #include "content/browser/web_package/web_bundle_reader.h"
...@@ -46,6 +47,9 @@ using DoneCallback = base::OnceCallback<void( ...@@ -46,6 +47,9 @@ using DoneCallback = base::OnceCallback<void(
const GURL& target_inner_url, const GURL& target_inner_url,
std::unique_ptr<WebBundleURLLoaderFactory> url_loader_factory)>; std::unique_ptr<WebBundleURLLoaderFactory> url_loader_factory)>;
constexpr char kNoSniffErrorMessage[] =
"Web Bundle response must have \"X-Content-Type-Options: nosniff\" header.";
const net::NetworkTrafficAnnotationTag kTrafficAnnotation = const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("web_bundle_start_url_loader", net::DefineNetworkTrafficAnnotation("web_bundle_start_url_loader",
R"( R"(
...@@ -463,6 +467,12 @@ class InterceptorForNetwork final : public NavigationLoaderInterceptor { ...@@ -463,6 +467,12 @@ class InterceptorForNetwork final : public NavigationLoaderInterceptor {
} }
*client_receiver = forwarding_client_.BindNewPipeAndPassReceiver(); *client_receiver = forwarding_client_.BindNewPipeAndPassReceiver();
if (!signed_exchange_utils::HasNoSniffHeader(**response_head)) {
CompleteWithInvalidWebBundleError(std::move(forwarding_client_),
frame_tree_node_id_,
kNoSniffErrorMessage);
return true;
}
auto source = WebBundleSource::MaybeCreateFromNetworkUrl(request.url); auto source = WebBundleSource::MaybeCreateFromNetworkUrl(request.url);
if (!source) { if (!source) {
CompleteWithInvalidWebBundleError( CompleteWithInvalidWebBundleError(
...@@ -1041,6 +1051,12 @@ class InterceptorForHistoryNavigationFromNetwork final ...@@ -1041,6 +1051,12 @@ class InterceptorForHistoryNavigationFromNetwork final
"Unexpected content type."); "Unexpected content type.");
return true; return true;
} }
if (!signed_exchange_utils::HasNoSniffHeader(**response_head)) {
CompleteWithInvalidWebBundleError(std::move(forwarding_client_),
frame_tree_node_id_,
kNoSniffErrorMessage);
return true;
}
uint64_t length_hint = uint64_t length_hint =
(*response_head)->content_length > 0 (*response_head)->content_length > 0
? static_cast<uint64_t>((*response_head)->content_length) ? static_cast<uint64_t>((*response_head)->content_length)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment