Commit 44cd9a89 authored by Kunihiko Sakamoto's avatar Kunihiko Sakamoto Committed by Commit Bot

SignedEchange: Fallback if additional content-encodings are applied

Before this patch, Content-Encoding header of inner response was not
checked (content was assumed to be "mi-sha256-03" encoded).

Currently we only support "mi-sha256-03" inner encoding, so this patch
makes SignedExchangeHandler reject (causing fallback redirect) the
signed exchange if its inner Content-Encoding is not just "mi-sha256-03".

This also refactors the logic to check integrity scheme, by moving them
into single place, SignedExchangeHandler::CreateResponseBodyStream().

Note: This shouldn't affect amppackager, because it strips additional
content-encodings in b3.
https://github.com/ampproject/amppackager/issues/243

Bug: 931055, 934629
Change-Id: Icc0bde36194d4a387ab30968cccf1bef4c81fe60
Reviewed-on: https://chromium-review.googlesource.com/c/1482351Reviewed-by: default avatarTsuyoshi Horo <horo@chromium.org>
Reviewed-by: default avatarKouhei Ueno <kouhei@chromium.org>
Commit-Queue: Kunihiko Sakamoto <ksakamoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#635413}
parent 255e5ac5
...@@ -262,13 +262,6 @@ bool ParseResponseMap(const cbor::Value& value, ...@@ -262,13 +262,6 @@ bool ParseResponseMap(const cbor::Value& value,
return false; return false;
} }
found = out->response_headers().find("digest");
if (found == out->response_headers().end()) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy, "Signed exchange has no Digest: header");
return false;
}
return true; return true;
} }
......
...@@ -18,8 +18,6 @@ SignedExchangeError::GetFieldFromSignatureVerifierResult( ...@@ -18,8 +18,6 @@ SignedExchangeError::GetFieldFromSignatureVerifierResult(
case SignedExchangeSignatureVerifier::Result:: case SignedExchangeSignatureVerifier::Result::
kErrSignatureVerificationFailed: kErrSignatureVerificationFailed:
return Field::kSignatureSig; return Field::kSignatureSig;
case SignedExchangeSignatureVerifier::Result::kErrInvalidSignatureIntegrity:
return Field::kSignatureIintegrity;
case SignedExchangeSignatureVerifier::Result::kErrUnsupportedCertType: case SignedExchangeSignatureVerifier::Result::kErrUnsupportedCertType:
return Field::kSignatureCertUrl; return Field::kSignatureCertUrl;
case SignedExchangeSignatureVerifier::Result::kErrValidityPeriodTooLong: case SignedExchangeSignatureVerifier::Result::kErrValidityPeriodTooLong:
...@@ -33,6 +31,8 @@ SignedExchangeError::GetFieldFromSignatureVerifierResult( ...@@ -33,6 +31,8 @@ SignedExchangeError::GetFieldFromSignatureVerifierResult(
kErrNoCertificateSHA256_deprecated: kErrNoCertificateSHA256_deprecated:
case SignedExchangeSignatureVerifier::Result:: case SignedExchangeSignatureVerifier::Result::
kErrInvalidSignatureFormat_deprecated: kErrInvalidSignatureFormat_deprecated:
case SignedExchangeSignatureVerifier::Result::
kErrInvalidSignatureIntegrity_deprecated:
case SignedExchangeSignatureVerifier::Result:: case SignedExchangeSignatureVerifier::Result::
kErrInvalidTimestamp_deprecated: kErrInvalidTimestamp_deprecated:
NOTREACHED(); NOTREACHED();
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "content/browser/web_package/signed_exchange_handler.h" #include "content/browser/web_package/signed_exchange_handler.h"
#include <memory>
#include <utility> #include <utility>
#include "base/bind.h" #include "base/bind.h"
...@@ -53,7 +54,7 @@ namespace content { ...@@ -53,7 +54,7 @@ namespace content {
namespace { namespace {
constexpr char kDigestHeader[] = "Digest"; constexpr char kDigestHeader[] = "digest";
constexpr char kHistogramSignatureVerificationResult[] = constexpr char kHistogramSignatureVerificationResult[] =
"SignedExchange.SignatureVerificationResult"; "SignedExchange.SignatureVerificationResult";
constexpr char kHistogramCertVerificationResult[] = constexpr char kHistogramCertVerificationResult[] =
...@@ -653,11 +654,12 @@ void SignedExchangeHandler::OnVerifyCert( ...@@ -653,11 +654,12 @@ void SignedExchangeHandler::OnVerifyCert(
response_head.load_timing.send_end = now; response_head.load_timing.send_end = now;
response_head.load_timing.receive_headers_end = now; response_head.load_timing.receive_headers_end = now;
std::string digest_header_value; auto body_stream = CreateResponseBodyStream();
response_head.headers->EnumerateHeader(nullptr, kDigestHeader, if (!body_stream) {
&digest_header_value); RunErrorCallback(SignedExchangeLoadResult::kInvalidIntegrityHeader,
auto mi_stream = std::make_unique<MerkleIntegritySourceStream>( net::ERR_INVALID_SIGNED_EXCHANGE);
digest_header_value, std::move(source_)); return;
}
net::SSLInfo ssl_info; net::SSLInfo ssl_info;
ssl_info.cert = cv_result.verified_cert; ssl_info.cert = cv_result.verified_cert;
...@@ -679,8 +681,50 @@ void SignedExchangeHandler::OnVerifyCert( ...@@ -679,8 +681,50 @@ void SignedExchangeHandler::OnVerifyCert(
response_head.ssl_info = std::move(ssl_info); response_head.ssl_info = std::move(ssl_info);
std::move(headers_callback_) std::move(headers_callback_)
.Run(SignedExchangeLoadResult::kSuccess, net::OK, .Run(SignedExchangeLoadResult::kSuccess, net::OK,
envelope_->request_url().url, response_head, std::move(mi_stream)); envelope_->request_url().url, response_head, std::move(body_stream));
state_ = State::kHeadersCallbackCalled; state_ = State::kHeadersCallbackCalled;
} }
// https://wicg.github.io/webpackage/loading.html#read-a-body
std::unique_ptr<net::SourceStream>
SignedExchangeHandler::CreateResponseBodyStream() {
if (!base::EqualsCaseInsensitiveASCII(envelope_->signature().integrity,
"digest/mi-sha256-03")) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(),
"The current implemention only supports \"digest/mi-sha256-03\" "
"integrity scheme.",
std::make_pair(0 /* signature_index */,
SignedExchangeError::Field::kSignatureIintegrity));
return nullptr;
}
const auto& headers = envelope_->response_headers();
auto digest_iter = headers.find(kDigestHeader);
if (digest_iter == headers.end()) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(), "Signed exchange has no Digest: header");
return nullptr;
}
// For now, we allow only mi-sha256-03 content encoding.
// TODO(crbug.com/934629): Handle other content codings, such as gzip and br.
auto content_encoding_iter = headers.find("content-encoding");
if (content_encoding_iter == headers.end()) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(),
"Signed exchange has no Content-Encoding: header");
return nullptr;
}
if (!base::LowerCaseEqualsASCII(content_encoding_iter->second,
"mi-sha256-03")) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(),
"Exchange's Content-Encoding must be \"mi-sha256-03\".");
return nullptr;
}
return std::make_unique<MerkleIntegritySourceStream>(digest_iter->second,
std::move(source_));
}
} // namespace content } // namespace content
...@@ -128,6 +128,7 @@ class CONTENT_EXPORT SignedExchangeHandler { ...@@ -128,6 +128,7 @@ class CONTENT_EXPORT SignedExchangeHandler {
void OnVerifyCert(int32_t error_code, void OnVerifyCert(int32_t error_code,
const net::CertVerifyResult& cv_result, const net::CertVerifyResult& cv_result,
const net::ct::CTVerifyResult& ct_result); const net::ct::CTVerifyResult& ct_result);
std::unique_ptr<net::SourceStream> CreateResponseBodyStream();
const bool is_secure_transport_; const bool is_secure_transport_;
const bool has_nosniff_; const bool has_nosniff_;
......
...@@ -460,6 +460,41 @@ TEST_P(SignedExchangeHandlerTest, MimeType) { ...@@ -460,6 +460,41 @@ TEST_P(SignedExchangeHandlerTest, MimeType) {
EXPECT_EQ(rv, static_cast<int>(expected_payload.size())); EXPECT_EQ(rv, static_cast<int>(expected_payload.size()));
} }
TEST_P(SignedExchangeHandlerTest, AdditionalContentEncodingShouldBeRejected) {
mock_cert_fetcher_factory_->ExpectFetch(
GURL("https://cert.example.org/cert.msg"),
GetTestFileContents("test.example.org.public.pem.cbor"));
// Make the MockCertVerifier treat the certificate
// "prime256v1-sha256.public.pem" as valid for "test.example.org".
scoped_refptr<net::X509Certificate> original_cert =
LoadCertificate("prime256v1-sha256.public.pem");
net::CertVerifyResult dummy_result;
dummy_result.verified_cert = original_cert;
dummy_result.cert_status = net::OK;
dummy_result.ocsp_result.response_status = net::OCSPVerifyResult::PROVIDED;
dummy_result.ocsp_result.revocation_status = net::OCSPRevocationStatus::GOOD;
auto mock_cert_verifier = std::make_unique<net::MockCertVerifier>();
mock_cert_verifier->AddResultForCertAndHost(original_cert, "test.example.org",
dummy_result, net::OK);
SetCertVerifier(std::move(mock_cert_verifier));
std::string contents =
GetTestFileContents("test.example.org_test.html.gz.sxg");
source_->AddReadResult(contents.data(), contents.size(), net::OK, GetParam());
source_->AddReadResult(nullptr, 0, net::OK, GetParam());
CreateSignedExchangeHandler(CreateTestURLRequestContext());
WaitForHeader();
ASSERT_TRUE(read_header());
EXPECT_EQ(SignedExchangeLoadResult::kInvalidIntegrityHeader, result());
EXPECT_EQ(net::ERR_INVALID_SIGNED_EXCHANGE, error());
EXPECT_EQ(kTestSxgInnerURL, inner_url());
// Drain the MockSourceStream, otherwise its destructer causes DCHECK failure.
ReadStream(source_, nullptr);
}
TEST_P(SignedExchangeHandlerTest, HeaderParseError) { TEST_P(SignedExchangeHandlerTest, HeaderParseError) {
const uint8_t data[] = {'s', 'x', 'g', '1', '-', 'b', '2', '\0', const uint8_t data[] = {'s', 'x', 'g', '1', '-', 'b', '2', '\0',
0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00}; 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00};
......
...@@ -318,15 +318,6 @@ SignedExchangeSignatureVerifier::Result SignedExchangeSignatureVerifier::Verify( ...@@ -318,15 +318,6 @@ SignedExchangeSignatureVerifier::Result SignedExchangeSignatureVerifier::Verify(
devtools_proxy, "Failed to verify signature \"sig\"."); devtools_proxy, "Failed to verify signature \"sig\".");
return Result::kErrSignatureVerificationFailed; return Result::kErrSignatureVerificationFailed;
} }
if (!base::EqualsCaseInsensitiveASCII(envelope.signature().integrity,
"digest/mi-sha256-03")) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy,
"The current implemention only supports \"digest/mi-sha256-03\" "
"integrity scheme.");
return Result::kErrInvalidSignatureIntegrity;
}
return Result::kSuccess; return Result::kSuccess;
} }
......
...@@ -42,7 +42,7 @@ class CONTENT_EXPORT SignedExchangeSignatureVerifier final { ...@@ -42,7 +42,7 @@ class CONTENT_EXPORT SignedExchangeSignatureVerifier final {
kErrCertificateSHA256Mismatch, kErrCertificateSHA256Mismatch,
kErrInvalidSignatureFormat_deprecated, kErrInvalidSignatureFormat_deprecated,
kErrSignatureVerificationFailed, kErrSignatureVerificationFailed,
kErrInvalidSignatureIntegrity, kErrInvalidSignatureIntegrity_deprecated,
kErrInvalidTimestamp_deprecated, kErrInvalidTimestamp_deprecated,
kErrUnsupportedCertType, kErrUnsupportedCertType,
kErrValidityPeriodTooLong, kErrValidityPeriodTooLong,
......
...@@ -118,14 +118,6 @@ SignedExchangeLoadResult GetLoadResultFromSignatureVerifierResult( ...@@ -118,14 +118,6 @@ SignedExchangeLoadResult GetLoadResultFromSignatureVerifierResult(
// requestUrlBytes, and signed exchange version version, return // requestUrlBytes, and signed exchange version version, return
// "signature_verification_error"." [spec text] // "signature_verification_error"." [spec text]
return SignedExchangeLoadResult::kSignatureVerificationError; return SignedExchangeLoadResult::kSignatureVerificationError;
case SignedExchangeSignatureVerifier::Result::kErrInvalidSignatureIntegrity:
// "Creating the response stream.
// - If signature’s integrity header is:
// - "digest", "mi-sha256-03"
// ...
// - Anything else
// Return an error string "invalid_integrity_header". " [spec text]
return SignedExchangeLoadResult::kInvalidIntegrityHeader;
case SignedExchangeSignatureVerifier::Result::kErrUnsupportedCertType: case SignedExchangeSignatureVerifier::Result::kErrUnsupportedCertType:
// "Validating a signature // "Validating a signature
// ... // ...
...@@ -172,6 +164,8 @@ SignedExchangeLoadResult GetLoadResultFromSignatureVerifierResult( ...@@ -172,6 +164,8 @@ SignedExchangeLoadResult GetLoadResultFromSignatureVerifierResult(
kErrNoCertificateSHA256_deprecated: kErrNoCertificateSHA256_deprecated:
case SignedExchangeSignatureVerifier::Result:: case SignedExchangeSignatureVerifier::Result::
kErrInvalidSignatureFormat_deprecated: kErrInvalidSignatureFormat_deprecated:
case SignedExchangeSignatureVerifier::Result::
kErrInvalidSignatureIntegrity_deprecated:
case SignedExchangeSignatureVerifier::Result:: case SignedExchangeSignatureVerifier::Result::
kErrInvalidTimestamp_deprecated: kErrInvalidTimestamp_deprecated:
NOTREACHED(); NOTREACHED();
......
...@@ -111,6 +111,21 @@ gen-signedexchange \ ...@@ -111,6 +111,21 @@ gen-signedexchange \
-date 2018-03-12T05:53:20Z \ -date 2018-03-12T05:53:20Z \
-o test.example.org_hello.txt.sxg -o test.example.org_hello.txt.sxg
# Generate the signed exchange whose content is gzip-encoded.
gzip -c test.html >$tmpdir/test.html.gz
gen-signedexchange \
-version 1b3 \
-uri https://test.example.org/test/ \
-status 200 \
-content $tmpdir/test.html.gz \
-certificate prime256v1-sha256.public.pem \
-certUrl https://cert.example.org/cert.msg \
-validityUrl https://test.example.org/resource.validity.msg \
-privateKey prime256v1.key \
-responseHeader 'Content-Encoding: gzip' \
-date 2018-03-12T05:53:20Z \
-o test.example.org_test.html.gz.sxg
echo "Update the test signatures in " echo "Update the test signatures in "
echo "signed_exchange_signature_verifier_unittest.cc with the followings:" echo "signed_exchange_signature_verifier_unittest.cc with the followings:"
echo "====" echo "===="
......
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