blob: b068987cc1fe6d9c8fcb184d15770666c5873c51 [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/strings/stringprintf.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_certificate_chain.h"
#include "content/browser/web_package/signed_exchange_devtools_proxy.h"
#include "content/browser/web_package/signed_exchange_envelope.h"
#include "content/browser/web_package/signed_exchange_prologue.h"
#include "content/browser/web_package/signed_exchange_signature_verifier.h"
#include "content/browser/web_package/signed_exchange_utils.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.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/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/cert/asn1_util.h"
#include "net/cert/cert_status_flags.h"
#include "net/cert/cert_verifier.h"
#include "net/cert/ct_policy_enforcer.h"
#include "net/cert/ct_verifier.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/filter/source_stream.h"
#include "net/http/transport_security_state.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 {
constexpr char kMiHeader[] = "MI-Draft2";
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,
int load_flags,
scoped_refptr<net::URLRequestContextGetter> request_context_getter,
std::unique_ptr<SignedExchangeDevToolsProxy> devtools_proxy)
: headers_callback_(std::move(headers_callback)),
source_(std::move(body)),
cert_fetcher_factory_(std::move(cert_fetcher_factory)),
load_flags_(load_flags),
request_context_getter_(std::move(request_context_getter)),
net_log_(net::NetLogWithSource::Make(
request_context_getter_->GetURLRequestContext()->net_log(),
net::NetLogSourceType::CERT_VERIFIER_JOB)),
devtools_proxy_(std::move(devtools_proxy)),
weak_factory_(this) {
DCHECK(signed_exchange_utils::IsSignedExchangeHandlingEnabled());
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::SignedExchangeHandler");
if (!SignedExchangeSignatureHeaderField::GetVersionParamFromContentType(
content_type, &version_) ||
version_ != SignedExchangeVersion::kB1) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&SignedExchangeHandler::RunErrorCallback,
weak_factory_.GetWeakPtr(),
net::ERR_INVALID_SIGNED_EXCHANGE));
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(),
base::StringPrintf("Unsupported version of the content type. Currentry "
"content type must be "
"\"application/signed-exchange;v=b1\". But the "
"response content type was \"%s\"",
content_type.c_str()));
return;
}
// Triggering the read (asynchronously) for the prologue bytes.
SetupBuffers(SignedExchangePrologue::kEncodedPrologueInBytes);
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&SignedExchangeHandler::DoHeaderLoop,
weak_factory_.GetWeakPtr()));
}
SignedExchangeHandler::~SignedExchangeHandler() = default;
SignedExchangeHandler::SignedExchangeHandler()
: load_flags_(net::LOAD_NORMAL), 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::kReadingPrologue || 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) {
DCHECK(state_ == State::kReadingPrologue || state_ == State::kReadingHeaders);
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::DidReadHeader");
if (result < 0) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(),
base::StringPrintf("Error reading body stream. result: %d", result));
RunErrorCallback(static_cast<net::Error>(result));
return;
}
if (result == 0) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(),
"Stream ended while reading signed exchange header.");
RunErrorCallback(net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
header_read_buf_->DidConsume(result);
if (header_read_buf_->BytesRemaining() == 0) {
switch (state_) {
case State::kReadingPrologue:
if (!ParsePrologue()) {
RunErrorCallback(net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
break;
case State::kReadingHeaders:
if (!ParseHeadersAndFetchCertificate()) {
RunErrorCallback(net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
break;
default:
NOTREACHED();
}
}
// We have finished reading headers, so return without queueing the next read.
if (state_ == State::kFetchingCertificate)
return;
// Trigger the next read.
DCHECK(state_ == State::kReadingPrologue || state_ == State::kReadingHeaders);
if (completed_syncly) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&SignedExchangeHandler::DoHeaderLoop,
weak_factory_.GetWeakPtr()));
} else {
DoHeaderLoop();
}
}
bool SignedExchangeHandler::ParsePrologue() {
DCHECK_EQ(state_, State::kReadingPrologue);
prologue_ = SignedExchangePrologue::Parse(
base::make_span(reinterpret_cast<uint8_t*>(header_buf_->data()),
SignedExchangePrologue::kEncodedPrologueInBytes),
devtools_proxy_.get());
if (!prologue_)
return false;
// Set up a new buffer for Signature + CBOR-encoded header reading.
SetupBuffers(prologue_->ComputeFollowingSectionsLength());
state_ = State::kReadingHeaders;
return true;
}
bool SignedExchangeHandler::ParseHeadersAndFetchCertificate() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::ParseHeadersAndFetchCertificate");
DCHECK_EQ(state_, State::kReadingHeaders);
base::StringPiece data(header_buf_->data(), header_read_buf_->size());
base::StringPiece signature_header_field =
data.substr(0, prologue_->signature_header_field_length());
base::span<const uint8_t> cbor_header = base::as_bytes(
base::make_span(data.substr(prologue_->signature_header_field_length(),
prologue_->cbor_header_length())));
envelope_ = SignedExchangeEnvelope::Parse(signature_header_field, cbor_header,
devtools_proxy_.get());
header_read_buf_ = nullptr;
header_buf_ = nullptr;
if (!envelope_) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(), "Failed to parse SignedExchange header.");
return false;
}
const GURL cert_url = envelope_->signature().cert_url;
// TODO(https://crbug.com/819467): When we will support ed25519Key, |cert_url|
// may be empty.
DCHECK(cert_url.is_valid());
DCHECK(version_.has_value());
DCHECK(cert_fetcher_factory_);
const bool force_fetch = load_flags_ & net::LOAD_BYPASS_CACHE;
cert_fetcher_ = std::move(cert_fetcher_factory_)
->CreateFetcherAndStart(
cert_url, force_fetch, *version_,
base::BindOnce(&SignedExchangeHandler::OnCertReceived,
base::Unretained(this)),
devtools_proxy_.get());
state_ = State::kFetchingCertificate;
return true;
}
void SignedExchangeHandler::RunErrorCallback(net::Error error) {
DCHECK_NE(state_, State::kHeadersCallbackCalled);
if (devtools_proxy_) {
devtools_proxy_->OnSignedExchangeReceived(
envelope_,
unverified_cert_chain_ ? unverified_cert_chain_->cert()
: scoped_refptr<net::X509Certificate>(),
nullptr);
}
std::move(headers_callback_)
.Run(error, GURL(), std::string(), network::ResourceResponseHead(),
nullptr);
state_ = State::kHeadersCallbackCalled;
}
void SignedExchangeHandler::OnCertReceived(
std::unique_ptr<SignedExchangeCertificateChain> cert_chain) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::OnCertReceived");
DCHECK_EQ(state_, State::kFetchingCertificate);
if (!cert_chain) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(), "Failed to fetch the certificate.",
std::make_pair(0 /* signature_index */,
SignedExchangeError::Field::kSignatureCertUrl));
RunErrorCallback(net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
unverified_cert_chain_ = std::move(cert_chain);
const SignedExchangeSignatureVerifier::Result verify_result =
SignedExchangeSignatureVerifier::Verify(
*envelope_, unverified_cert_chain_->cert(), GetVerificationTime(),
devtools_proxy_.get());
if (verify_result != SignedExchangeSignatureVerifier::Result::kSuccess) {
base::Optional<SignedExchangeError::Field> error_field =
SignedExchangeError::GetFieldFromSignatureVerifierResult(verify_result);
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(), "Failed to verify the signed exchange header.",
error_field ? base::make_optional(
std::make_pair(0 /* signature_index */, *error_field))
: base::nullopt);
RunErrorCallback(net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
// TODO(https://crbug.com/849935): The code below reaching into
// URLRequestContext will not work with Network service.
DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService));
net::URLRequestContext* request_context =
request_context_getter_->GetURLRequestContext();
if (!request_context) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(), "No request context available.");
RunErrorCallback(net::ERR_CONTEXT_SHUT_DOWN);
return;
}
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();
int result = cert_verifier->Verify(
net::CertVerifier::RequestParams(
unverified_cert_chain_->cert(), envelope_->request_url().host(),
config.GetCertVerifyFlags(), unverified_cert_chain_->ocsp(),
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);
}
bool SignedExchangeHandler::CheckCertExtension(
const net::X509Certificate* verified_cert) {
if (base::FeatureList::IsEnabled(
features::kAllowSignedHTTPExchangeCertsWithoutExtension))
return true;
// https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#cross-origin-trust
// Step 6.2. Validate that main-certificate has the CanSignHttpExchanges
// extension (Section 4.2). [spec text]
return net::asn1::HasCanSignHttpExchangesDraftExtension(
net::x509_util::CryptoBufferAsStringPiece(verified_cert->cert_buffer()));
}
bool SignedExchangeHandler::CheckOCSPStatus(
const net::OCSPVerifyResult& ocsp_result) {
// https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#cross-origin-trust
// Step 6.3 Validate that main-certificate has an ocsp property (Section 3.3)
// with a valid OCSP response whose lifetime (nextUpdate - thisUpdate) is less
// than 7 days ([RFC6960]). [spec text]
//
// OCSP verification is done in CertVerifier::Verify(), so we just check the
// result here.
if (ocsp_result.response_status != net::OCSPVerifyResult::PROVIDED ||
ocsp_result.revocation_status != net::OCSPRevocationStatus::GOOD)
return false;
return true;
}
// TODO(https://crbug.com/815025, https://crbug.com/849935): This is temporary
// code until we have Network Service friendly CT verification.
int SignedExchangeHandler::VerifyCT(net::ct::CTVerifyResult* ct_verify_result) {
// This function will not work with Network Service.
DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService));
net::URLRequestContext* request_context =
request_context_getter_->GetURLRequestContext();
if (!request_context)
return net::ERR_CONTEXT_SHUT_DOWN;
// https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#cross-origin-trust
// Step 6.4 Validate that valid SCTs from trusted logs are available from any
// of:
// - The SignedCertificateTimestampList in main-certificate’s sct property
// (Section 3.3),
const std::string& sct_list_from_cert_cbor = unverified_cert_chain_->sct();
// - An OCSP extension in the OCSP response in main-certificate’s ocsp
// property, or
const std::string& stapled_ocsp_response = unverified_cert_chain_->ocsp();
// - An X.509 extension in the certificate in main-certificate’s cert
// property,
net::X509Certificate* verified_cert = cert_verify_result_.verified_cert.get();
// as described by Section 3.3 of [RFC6962]. [spec text]
request_context->cert_transparency_verifier()->Verify(
envelope_->request_url().host(), verified_cert, stapled_ocsp_response,
sct_list_from_cert_cbor, &ct_verify_result->scts, net_log_);
net::ct::SCTList verified_scts = net::ct::SCTsMatchingStatus(
ct_verify_result->scts, net::ct::SCT_STATUS_OK);
ct_verify_result->policy_compliance =
request_context->ct_policy_enforcer()->CheckCompliance(
verified_cert, verified_scts, net_log_);
// TODO(https://crbug.com/803774): We should determine whether EV & SXG
// should be a thing (due to the online/offline signing difference)
if (cert_verify_result_.cert_status & net::CERT_STATUS_IS_EV &&
ct_verify_result->policy_compliance !=
net::ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS &&
ct_verify_result->policy_compliance !=
net::ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY) {
cert_verify_result_.cert_status |= net::CERT_STATUS_CT_COMPLIANCE_FAILED;
cert_verify_result_.cert_status &= ~net::CERT_STATUS_IS_EV;
}
net::TransportSecurityState::CTRequirementsStatus ct_requirement_status =
request_context->transport_security_state()->CheckCTRequirements(
net::HostPortPair::FromURL(envelope_->request_url()),
cert_verify_result_.is_issued_by_known_root,
cert_verify_result_.public_key_hashes, verified_cert,
unverified_cert_chain_->cert().get(), ct_verify_result->scts,
net::TransportSecurityState::ENABLE_EXPECT_CT_REPORTS,
ct_verify_result->policy_compliance);
switch (ct_requirement_status) {
case net::TransportSecurityState::CT_REQUIREMENTS_NOT_MET:
return net::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED;
case net::TransportSecurityState::CT_REQUIREMENTS_MET:
ct_verify_result->policy_compliance_required = true;
break;
case net::TransportSecurityState::CT_NOT_REQUIRED:
// CT is not required if the certificate does not chain to a publicly
// trusted root certificate.
if (!cert_verify_result_.is_issued_by_known_root) {
ct_verify_result->policy_compliance_required = false;
break;
}
// For old certificates (issued before 2018-05-01), CheckCTRequirements()
// may return CT_NOT_REQUIRED, so we check the compliance status here.
// TODO(https://crbug.com/851778): Remove this condition once we require
// signing certificates to have CanSignHttpExchanges extension, because
// such certificates should be naturally after 2018-05-01.
if (ct_verify_result->policy_compliance ==
net::ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS ||
ct_verify_result->policy_compliance ==
net::ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY) {
ct_verify_result->policy_compliance_required = true;
break;
}
// Require CT compliance, by overriding CT_NOT_REQUIRED and treat it as
// ERR_CERTIFICATE_TRANSPARENCY_REQUIRED.
return net::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED;
}
return net::OK;
}
void SignedExchangeHandler::OnCertVerifyComplete(int result) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::OnCertVerifyComplete");
if (result != net::OK) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(),
base::StringPrintf("Certificate verification error: %s",
net::ErrorToShortString(result).c_str()),
std::make_pair(0 /* signature_index */,
SignedExchangeError::Field::kSignatureCertUrl));
RunErrorCallback(net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
if (!CheckCertExtension(cert_verify_result_.verified_cert.get())) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(),
"Certificate must have CanSignHttpExchangesDraft extension. To ignore "
"this error for testing, enable "
"chrome://flags/#allow-sxg-certs-without-extension.",
std::make_pair(0 /* signature_index */,
SignedExchangeError::Field::kSignatureCertUrl));
RunErrorCallback(net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
if (!CheckOCSPStatus(cert_verify_result_.ocsp_result)) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(),
base::StringPrintf(
"OCSP check failed. response status: %d, revocation status: %d",
cert_verify_result_.ocsp_result.response_status,
cert_verify_result_.ocsp_result.revocation_status),
std::make_pair(0 /* signature_index */,
SignedExchangeError::Field::kSignatureCertUrl));
RunErrorCallback(net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
net::ct::CTVerifyResult ct_verify_result;
int ct_result = VerifyCT(&ct_verify_result);
if (ct_result != net::OK) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(),
base::StringPrintf(
"CT verification failed. result: %s, policy compliance: %d",
net::ErrorToShortString(ct_result).c_str(),
ct_verify_result.policy_compliance),
std::make_pair(0 /* signature_index */,
SignedExchangeError::Field::kSignatureCertUrl));
RunErrorCallback(net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
network::ResourceResponseHead response_head;
response_head.headers = envelope_->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)) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(), "Signed exchange has no MI-Draft2: header");
RunErrorCallback(net::ERR_INVALID_SIGNED_EXCHANGE);
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_chain_->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);
ssl_info.UpdateCertificateTransparencyInfo(ct_verify_result);
if (devtools_proxy_) {
devtools_proxy_->OnSignedExchangeReceived(
envelope_, unverified_cert_chain_->cert(), &ssl_info);
}
response_head.ssl_info = std::move(ssl_info);
std::move(headers_callback_)
.Run(net::OK, envelope_->request_url(), envelope_->request_method(),
response_head, std::move(mi_stream));
state_ = State::kHeadersCallbackCalled;
}
} // namespace content