blob: 8ba3623c89061b1925f03834e330694e2b164237 [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_handler.h"
#include "base/feature_list.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/loader/merkle_integrity_source_stream.h"
#include "content/browser/web_package/signed_exchange_cert_fetcher_factory.h"
#include "content/browser/web_package/signed_exchange_consts.h"
#include "content/browser/web_package/signed_exchange_header.h"
#include "content/browser/web_package/signed_exchange_signature_verifier.h"
#include "content/public/common/content_features.h"
#include "content/public/common/url_loader_throttle.h"
#include "mojo/public/cpp/system/string_data_pipe_producer.h"
#include "net/base/io_buffer.h"
#include "net/cert/cert_status_flags.h"
#include "net/cert/cert_verifier.h"
#include "net/cert/x509_certificate.h"
#include "net/filter/source_stream.h"
#include "net/ssl/ssl_config.h"
#include "net/ssl/ssl_config_service.h"
#include "net/ssl/ssl_info.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/resource_response.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
namespace content {
namespace {
// 256KB (Maximum header size) * 2, since signed exchange header contains
// request and response headers.
constexpr size_t kMaxHeadersCBORLength = 512 * 1024;
constexpr char kMiHeader[] = "MI";
net::CertVerifier* g_cert_verifier_for_testing = nullptr;
base::Optional<base::Time> g_verification_time_for_testing;
base::Time GetVerificationTime() {
if (g_verification_time_for_testing)
return *g_verification_time_for_testing;
return base::Time::Now();
}
} // namespace
// static
void SignedExchangeHandler::SetCertVerifierForTesting(
net::CertVerifier* cert_verifier) {
g_cert_verifier_for_testing = cert_verifier;
}
// static
void SignedExchangeHandler::SetVerificationTimeForTesting(
base::Optional<base::Time> verification_time_for_testing) {
g_verification_time_for_testing = verification_time_for_testing;
}
SignedExchangeHandler::SignedExchangeHandler(
std::string content_type,
std::unique_ptr<net::SourceStream> body,
ExchangeHeadersCallback headers_callback,
std::unique_ptr<SignedExchangeCertFetcherFactory> cert_fetcher_factory,
scoped_refptr<net::URLRequestContextGetter> request_context_getter)
: headers_callback_(std::move(headers_callback)),
source_(std::move(body)),
cert_fetcher_factory_(std::move(cert_fetcher_factory)),
request_context_getter_(std::move(request_context_getter)),
net_log_(net::NetLogWithSource::Make(
request_context_getter_->GetURLRequestContext()->net_log(),
net::NetLogSourceType::CERT_VERIFIER_JOB)),
weak_factory_(this) {
DCHECK(base::FeatureList::IsEnabled(features::kSignedHTTPExchange));
TRACE_EVENT_BEGIN0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::SignedExchangeHandler");
base::Optional<std::string> content_type_version_param;
if (!SignedExchangeHeaderParser::GetVersionParamFromContentType(
content_type, &content_type_version_param) ||
!content_type_version_param || *content_type_version_param != "b0") {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&SignedExchangeHandler::RunErrorCallback,
weak_factory_.GetWeakPtr(), net::ERR_FAILED));
TRACE_EVENT_END2(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::SignedExchangeHandler", "error",
"Unsupported version of the content type. Currentry "
"content type must be "
"\"application/signed-exchange;v=b0\".",
"content-type", content_type);
return;
}
// Triggering the read (asynchronously) for the encoded header length.
SetupBuffers(SignedExchangeHeader::kEncodedHeaderLengthInBytes);
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&SignedExchangeHandler::DoHeaderLoop,
weak_factory_.GetWeakPtr()));
TRACE_EVENT_END0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeCertFetcher::SignedExchangeHandler");
}
SignedExchangeHandler::~SignedExchangeHandler() = default;
SignedExchangeHandler::SignedExchangeHandler() : weak_factory_(this) {}
void SignedExchangeHandler::SetupBuffers(size_t size) {
header_buf_ = base::MakeRefCounted<net::IOBuffer>(size);
header_read_buf_ =
base::MakeRefCounted<net::DrainableIOBuffer>(header_buf_.get(), size);
}
void SignedExchangeHandler::DoHeaderLoop() {
DCHECK(state_ == State::kReadingHeadersLength ||
state_ == State::kReadingHeaders);
int rv = source_->Read(
header_read_buf_.get(), header_read_buf_->BytesRemaining(),
base::BindRepeating(&SignedExchangeHandler::DidReadHeader,
base::Unretained(this), false /* sync */));
if (rv != net::ERR_IO_PENDING)
DidReadHeader(true /* sync */, rv);
}
void SignedExchangeHandler::DidReadHeader(bool completed_syncly, int result) {
TRACE_EVENT_BEGIN0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::DidReadHeader");
if (result < 0) {
RunErrorCallback(static_cast<net::Error>(result));
TRACE_EVENT_END2(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::DidReadHeader", "error",
"Error reading body stream.", "result", result);
return;
}
if (result == 0) {
RunErrorCallback(net::ERR_FAILED);
TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::DidReadHeader", "error",
"Stream ended while reading signed exchange header.");
return;
}
header_read_buf_->DidConsume(result);
if (header_read_buf_->BytesRemaining() == 0) {
switch (state_) {
case State::kReadingHeadersLength:
if (!ParseHeadersLength())
RunErrorCallback(net::ERR_FAILED);
break;
case State::kReadingHeaders:
if (!ParseHeadersAndFetchCertificate())
RunErrorCallback(net::ERR_FAILED);
break;
default:
NOTREACHED();
}
}
if (state_ != State::kReadingHeadersLength &&
state_ != State::kReadingHeaders) {
TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::DidReadHeader", "state",
static_cast<int>(state_));
return;
}
// Trigger the next read.
if (completed_syncly) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&SignedExchangeHandler::DoHeaderLoop,
weak_factory_.GetWeakPtr()));
} else {
DoHeaderLoop();
}
TRACE_EVENT_END0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeCertFetcher::DidReadHeader");
}
bool SignedExchangeHandler::ParseHeadersLength() {
TRACE_EVENT_BEGIN0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::ParseHeadersLength");
DCHECK_EQ(state_, State::kReadingHeadersLength);
headers_length_ = SignedExchangeHeader::ParseHeadersLength(
base::make_span(reinterpret_cast<uint8_t*>(header_buf_->data()),
SignedExchangeHeader::kEncodedHeaderLengthInBytes));
if (headers_length_ == 0 || headers_length_ > kMaxHeadersCBORLength) {
TRACE_EVENT_END2(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::ParseHeadersLength", "error",
"Invalid CBOR header length.", "headers_length",
headers_length_);
return false;
}
// Set up a new buffer for CBOR-encoded buffer reading.
SetupBuffers(headers_length_);
state_ = State::kReadingHeaders;
TRACE_EVENT_END0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeCertFetcher::ParseHeadersLength");
return true;
}
bool SignedExchangeHandler::ParseHeadersAndFetchCertificate() {
TRACE_EVENT_BEGIN0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::ParseHeadersAndFetchCertificate");
DCHECK_EQ(state_, State::kReadingHeaders);
header_ = SignedExchangeHeader::Parse(base::make_span(
reinterpret_cast<uint8_t*>(header_buf_->data()), headers_length_));
header_read_buf_ = nullptr;
header_buf_ = nullptr;
if (!header_) {
TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::ParseHeadersAndFetchCertificate",
"error", "Failed to parse SignedExchange header.");
return false;
}
const GURL cert_url = header_->signature().cert_url;
// TODO(https://crbug.com/819467): When we will support ed25519Key, |cert_url|
// may be empty.
DCHECK(cert_url.is_valid());
DCHECK(cert_fetcher_factory_);
cert_fetcher_ = std::move(cert_fetcher_factory_)
->CreateFetcherAndStart(
cert_url, false,
base::BindOnce(&SignedExchangeHandler::OnCertReceived,
base::Unretained(this)));
state_ = State::kFetchingCertificate;
TRACE_EVENT_END0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::ParseHeadersAndFetchCertificate");
return true;
}
void SignedExchangeHandler::RunErrorCallback(net::Error error) {
DCHECK_NE(state_, State::kHeadersCallbackCalled);
std::move(headers_callback_)
.Run(error, GURL(), std::string(), network::ResourceResponseHead(),
nullptr);
state_ = State::kHeadersCallbackCalled;
}
void SignedExchangeHandler::OnCertReceived(
scoped_refptr<net::X509Certificate> cert) {
TRACE_EVENT_BEGIN0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::OnCertReceived");
DCHECK_EQ(state_, State::kFetchingCertificate);
if (!cert) {
RunErrorCallback(net::ERR_FAILED);
TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::OnCertReceived", "error",
"Fetching certificate error.");
return;
}
if (SignedExchangeSignatureVerifier::Verify(*header_, cert,
GetVerificationTime()) !=
SignedExchangeSignatureVerifier::Result::kSuccess) {
RunErrorCallback(net::ERR_FAILED);
TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::OnCertReceived", "error",
"SignedExchangeSignatureVerifier failed.");
return;
}
net::URLRequestContext* request_context =
request_context_getter_->GetURLRequestContext();
if (!request_context) {
RunErrorCallback(net::ERR_CONTEXT_SHUT_DOWN);
TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::OnCertReceived", "error",
"No request context available.");
return;
}
unverified_cert_ = cert;
net::SSLConfig config;
request_context->ssl_config_service()->GetSSLConfig(&config);
net::CertVerifier* cert_verifier = g_cert_verifier_for_testing
? g_cert_verifier_for_testing
: request_context->cert_verifier();
// TODO(https://crbug.com/815024): Get the OCSP response from the
// “status_request” extension of the main-certificate, and check the lifetime
// (nextUpdate - thisUpdate) is less than 7 days.
int result = cert_verifier->Verify(
net::CertVerifier::RequestParams(
unverified_cert_, header_->request_url().host(),
config.GetCertVerifyFlags(), std::string() /* ocsp_response */,
net::CertificateList()),
net::SSLConfigService::GetCRLSet().get(), &cert_verify_result_,
base::BindRepeating(&SignedExchangeHandler::OnCertVerifyComplete,
base::Unretained(this)),
&cert_verifier_request_, net_log_);
// TODO(https://crbug.com/803774): Avoid these recursive patterns by using
// explicit state machines (eg: DoLoop() in //net).
if (result != net::ERR_IO_PENDING)
OnCertVerifyComplete(result);
TRACE_EVENT_END0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::OnCertReceived");
}
void SignedExchangeHandler::OnCertVerifyComplete(int result) {
TRACE_EVENT_BEGIN0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::OnCertVerifyComplete");
if (result != net::OK) {
RunErrorCallback(static_cast<net::Error>(result));
TRACE_EVENT_END2(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::OnCertVerifyComplete", "error",
"Certificate verification error.", "result", result);
return;
}
network::ResourceResponseHead response_head;
response_head.headers = header_->BuildHttpResponseHeaders();
response_head.headers->GetMimeTypeAndCharset(&response_head.mime_type,
&response_head.charset);
// TODO(https://crbug.com/803774): Resource timing for signed exchange
// loading is not speced yet. https://github.com/WICG/webpackage/issues/156
response_head.load_timing.request_start_time = base::Time::Now();
base::TimeTicks now(base::TimeTicks::Now());
response_head.load_timing.request_start = now;
response_head.load_timing.send_start = now;
response_head.load_timing.send_end = now;
response_head.load_timing.receive_headers_end = now;
std::string mi_header_value;
if (!response_head.headers->EnumerateHeader(nullptr, kMiHeader,
&mi_header_value)) {
RunErrorCallback(net::ERR_FAILED);
TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::OnCertVerifyComplete", "error",
"Signed exchange has no MI: header.");
return;
}
auto mi_stream = std::make_unique<MerkleIntegritySourceStream>(
mi_header_value, std::move(source_));
net::SSLInfo ssl_info;
ssl_info.cert = cert_verify_result_.verified_cert;
ssl_info.unverified_cert = unverified_cert_;
ssl_info.cert_status = cert_verify_result_.cert_status;
ssl_info.is_issued_by_known_root =
cert_verify_result_.is_issued_by_known_root;
ssl_info.public_key_hashes = cert_verify_result_.public_key_hashes;
ssl_info.ocsp_result = cert_verify_result_.ocsp_result;
ssl_info.is_fatal_cert_error =
net::IsCertStatusError(ssl_info.cert_status) &&
!net::IsCertStatusMinorError(ssl_info.cert_status);
response_head.ssl_info = std::move(ssl_info);
// TODO(https://crbug.com/815025): Verify the Certificate Transparency status.
std::move(headers_callback_)
.Run(net::OK, header_->request_url(), header_->request_method(),
response_head, std::move(mi_stream));
state_ = State::kHeadersCallbackCalled;
TRACE_EVENT_END0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::OnCertVerifyComplete");
}
} // namespace content