blob: 67b52a19165a06d3750d31af48eade755e7add80 [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 <utility>
#include "base/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
#include "base/task/post_task.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/loader/merkle_integrity_source_stream.h"
#include "content/browser/renderer_host/render_process_host_impl.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_reporter.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_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/storage_partition.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/bindings/callback_helpers.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/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/filter/source_stream.h"
#include "net/ssl/ssl_info.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"
#include "services/network/public/mojom/network_context.mojom.h"
namespace content {
namespace {
constexpr char kDigestHeader[] = "Digest";
constexpr char kHistogramSignatureVerificationResult[] =
"SignedExchange.SignatureVerificationResult";
constexpr char kHistogramCertVerificationResult[] =
"SignedExchange.CertVerificationResult";
constexpr char kHistogramCTVerificationResult[] =
"SignedExchange.CTVerificationResult";
constexpr char kHistogramOCSPResponseStatus[] =
"SignedExchange.OCSPResponseStatus";
constexpr char kHistogramOCSPRevocationStatus[] =
"SignedExchange.OCSPRevocationStatus";
constexpr char kSXGFromNonHTTPSErrorMessage[] =
"Signed exchange response from non secure origin is not supported.";
constexpr char kSXGWithoutNoSniffErrorMessage[] =
"Signed exchange response without \"X-Content-Type-Options: nosniff\" "
"header is not supported.";
network::mojom::NetworkContext* g_network_context_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();
}
bool IsSupportedSignedExchangeVersion(
const base::Optional<SignedExchangeVersion>& version) {
return version == SignedExchangeVersion::kB3;
}
using VerifyCallback = base::OnceCallback<void(int32_t,
const net::CertVerifyResult&,
const net::ct::CTVerifyResult&)>;
void OnVerifyCertUI(VerifyCallback callback,
int32_t error_code,
const net::CertVerifyResult& cv_result,
const net::ct::CTVerifyResult& ct_result) {
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(std::move(callback), error_code, cv_result, ct_result));
}
void VerifyCert(const scoped_refptr<net::X509Certificate>& certificate,
const GURL& url,
const std::string& ocsp_result,
const std::string& sct_list,
base::RepeatingCallback<int(void)> frame_tree_node_id_getter,
VerifyCallback callback) {
VerifyCallback wrapped_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
base::BindOnce(OnVerifyCertUI, std::move(callback)), net::ERR_FAILED,
net::CertVerifyResult(), net::ct::CTVerifyResult());
network::mojom::NetworkContext* network_context =
g_network_context_for_testing;
if (!network_context) {
auto* frame =
FrameTreeNode::GloballyFindByID(frame_tree_node_id_getter.Run());
if (!frame)
return;
network_context = frame->current_frame_host()
->GetProcess()
->GetStoragePartition()
->GetNetworkContext();
}
network_context->VerifyCertForSignedExchange(
certificate, url, ocsp_result, sct_list, std::move(wrapped_callback));
}
std::string OCSPErrorToString(const net::OCSPVerifyResult& ocsp_result) {
switch (ocsp_result.response_status) {
case net::OCSPVerifyResult::PROVIDED:
break;
case net::OCSPVerifyResult::NOT_CHECKED:
// This happens only in tests.
return "OCSP verification was not performed.";
case net::OCSPVerifyResult::MISSING:
return "No OCSP Response was stapled.";
case net::OCSPVerifyResult::ERROR_RESPONSE:
return "OCSP response did not have a SUCCESSFUL status.";
case net::OCSPVerifyResult::BAD_PRODUCED_AT:
return "OCSP Response was produced at outside the certificate "
"validity period.";
case net::OCSPVerifyResult::NO_MATCHING_RESPONSE:
return "OCSP Response did not match the certificate.";
case net::OCSPVerifyResult::INVALID_DATE:
return "OCSP Response was expired or not yet valid.";
case net::OCSPVerifyResult::PARSE_RESPONSE_ERROR:
return "OCSPResponse structure could not be parsed.";
case net::OCSPVerifyResult::PARSE_RESPONSE_DATA_ERROR:
return "OCSP ResponseData structure could not be parsed.";
}
switch (ocsp_result.revocation_status) {
case net::OCSPRevocationStatus::GOOD:
NOTREACHED();
break;
case net::OCSPRevocationStatus::REVOKED:
return "OCSP response indicates that the certificate is revoked.";
case net::OCSPRevocationStatus::UNKNOWN:
return "OCSP responder doesn't know about the certificate.";
}
NOTREACHED();
return std::string();
}
} // namespace
// static
void SignedExchangeHandler::SetNetworkContextForTesting(
network::mojom::NetworkContext* network_context) {
g_network_context_for_testing = network_context;
}
// static
void SignedExchangeHandler::SetVerificationTimeForTesting(
base::Optional<base::Time> verification_time_for_testing) {
g_verification_time_for_testing = verification_time_for_testing;
}
SignedExchangeHandler::SignedExchangeHandler(
bool is_secure_transport,
bool has_nosniff,
std::string content_type,
std::unique_ptr<net::SourceStream> body,
ExchangeHeadersCallback headers_callback,
std::unique_ptr<SignedExchangeCertFetcherFactory> cert_fetcher_factory,
int load_flags,
std::unique_ptr<SignedExchangeDevToolsProxy> devtools_proxy,
SignedExchangeReporter* reporter,
base::RepeatingCallback<int(void)> frame_tree_node_id_getter)
: is_secure_transport_(is_secure_transport),
has_nosniff_(has_nosniff),
headers_callback_(std::move(headers_callback)),
source_(std::move(body)),
cert_fetcher_factory_(std::move(cert_fetcher_factory)),
load_flags_(load_flags),
devtools_proxy_(std::move(devtools_proxy)),
reporter_(reporter),
frame_tree_node_id_getter_(frame_tree_node_id_getter),
weak_factory_(this) {
DCHECK(signed_exchange_utils::IsSignedExchangeHandlingEnabled());
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::SignedExchangeHandler");
// https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#privacy-considerations
// This can be difficult to determine when the exchange is being loaded from
// local disk, but when the client itself requested the exchange over a
// network it SHOULD require TLS ([I-D.ietf-tls-tls13]) or a successor
// transport layer, and MUST NOT accept exchanges transferred over plain HTTP
// without TLS. [spec text]
if (!is_secure_transport_) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(), kSXGFromNonHTTPSErrorMessage);
// Proceed to extract and redirect to the fallback URL.
}
// https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#seccons-content-sniffing
// To encourage servers to include the `X-Content-Type-Options: nosniff`
// header field, clients SHOULD reject signed exchanges served without it.
// [spec text]
if (!has_nosniff_) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(), kSXGWithoutNoSniffErrorMessage);
// Proceed to extract and redirect to the fallback URL.
}
version_ = signed_exchange_utils::GetSignedExchangeVersion(content_type);
if (!IsSupportedSignedExchangeVersion(version_)) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(),
base::StringPrintf("Unsupported version of the content type. Currently "
"content type must be "
"\"application/signed-exchange;v=b3\". But the "
"response content type was \"%s\"",
content_type.c_str()));
// Proceed to extract and redirect to the fallback URL.
}
// Triggering the read (asynchronously) for the prologue bytes.
SetupBuffers(
signed_exchange_prologue::BeforeFallbackUrl::kEncodedSizeInBytes);
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&SignedExchangeHandler::DoHeaderLoop,
weak_factory_.GetWeakPtr()));
}
SignedExchangeHandler::~SignedExchangeHandler() = default;
SignedExchangeHandler::SignedExchangeHandler()
: is_secure_transport_(true),
has_nosniff_(true),
load_flags_(net::LOAD_NORMAL),
weak_factory_(this) {}
const GURL& SignedExchangeHandler::GetFallbackUrl() const {
return prologue_fallback_url_and_after_.fallback_url().url;
}
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::kReadingPrologueBeforeFallbackUrl ||
state_ == State::kReadingPrologueFallbackUrlAndAfter ||
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 read_result) {
DCHECK(state_ == State::kReadingPrologueBeforeFallbackUrl ||
state_ == State::kReadingPrologueFallbackUrlAndAfter ||
state_ == State::kReadingHeaders);
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::DidReadHeader");
if (read_result < 0) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(),
base::StringPrintf("Error reading body stream. result: %d",
read_result));
RunErrorCallback(SignedExchangeLoadResult::kSXGHeaderNetError,
static_cast<net::Error>(read_result));
return;
}
if (read_result == 0) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(),
"Stream ended while reading signed exchange header.");
SignedExchangeLoadResult result =
GetFallbackUrl().is_valid()
? SignedExchangeLoadResult::kHeaderParseError
: SignedExchangeLoadResult::kFallbackURLParseError;
RunErrorCallback(result, net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
header_read_buf_->DidConsume(read_result);
if (header_read_buf_->BytesRemaining() == 0) {
SignedExchangeLoadResult result = SignedExchangeLoadResult::kSuccess;
switch (state_) {
case State::kReadingPrologueBeforeFallbackUrl:
result = ParsePrologueBeforeFallbackUrl();
break;
case State::kReadingPrologueFallbackUrlAndAfter:
result = ParsePrologueFallbackUrlAndAfter();
break;
case State::kReadingHeaders:
result = ParseHeadersAndFetchCertificate();
break;
default:
NOTREACHED();
}
if (result != SignedExchangeLoadResult::kSuccess) {
RunErrorCallback(result, net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
}
// We have finished reading headers, so return without queueing the next read.
if (state_ == State::kFetchingCertificate)
return;
// Trigger the next read.
DCHECK(state_ == State::kReadingPrologueBeforeFallbackUrl ||
state_ == State::kReadingPrologueFallbackUrlAndAfter ||
state_ == State::kReadingHeaders);
if (completed_syncly) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&SignedExchangeHandler::DoHeaderLoop,
weak_factory_.GetWeakPtr()));
} else {
DoHeaderLoop();
}
}
SignedExchangeLoadResult
SignedExchangeHandler::ParsePrologueBeforeFallbackUrl() {
DCHECK_EQ(state_, State::kReadingPrologueBeforeFallbackUrl);
prologue_before_fallback_url_ =
signed_exchange_prologue::BeforeFallbackUrl::Parse(
base::make_span(
reinterpret_cast<uint8_t*>(header_buf_->data()),
signed_exchange_prologue::BeforeFallbackUrl::kEncodedSizeInBytes),
devtools_proxy_.get());
// Note: We will proceed even if |!prologue_before_fallback_url_.is_valid()|
// to attempt reading `fallbackUrl`.
// Set up a new buffer for reading
// |signed_exchange_prologue::FallbackUrlAndAfter|.
SetupBuffers(
prologue_before_fallback_url_.ComputeFallbackUrlAndAfterLength());
state_ = State::kReadingPrologueFallbackUrlAndAfter;
return SignedExchangeLoadResult::kSuccess;
}
SignedExchangeLoadResult
SignedExchangeHandler::ParsePrologueFallbackUrlAndAfter() {
DCHECK_EQ(state_, State::kReadingPrologueFallbackUrlAndAfter);
prologue_fallback_url_and_after_ =
signed_exchange_prologue::FallbackUrlAndAfter::Parse(
base::make_span(
reinterpret_cast<uint8_t*>(header_buf_->data()),
prologue_before_fallback_url_.ComputeFallbackUrlAndAfterLength()),
prologue_before_fallback_url_, devtools_proxy_.get());
if (!GetFallbackUrl().is_valid())
return SignedExchangeLoadResult::kFallbackURLParseError;
if (!is_secure_transport_)
return SignedExchangeLoadResult::kSXGServedFromNonHTTPS;
if (!has_nosniff_)
return SignedExchangeLoadResult::kSXGServedWithoutNosniff;
// If the signed exchange version from content-type is unsupported or the
// prologue's magic string is incorrect, abort parsing and redirect to the
// fallback URL.
if (!IsSupportedSignedExchangeVersion(version_) ||
!prologue_before_fallback_url_.is_valid())
return SignedExchangeLoadResult::kVersionMismatch;
if (!prologue_fallback_url_and_after_.is_valid())
return SignedExchangeLoadResult::kHeaderParseError;
// Set up a new buffer for reading the Signature header field and CBOR-encoded
// headers.
SetupBuffers(
prologue_fallback_url_and_after_.ComputeFollowingSectionsLength());
state_ = State::kReadingHeaders;
return SignedExchangeLoadResult::kSuccess;
}
SignedExchangeLoadResult
SignedExchangeHandler::ParseHeadersAndFetchCertificate() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::ParseHeadersAndFetchCertificate");
DCHECK_EQ(state_, State::kReadingHeaders);
DCHECK(version_.has_value());
base::StringPiece data(header_buf_->data(), header_read_buf_->size());
base::StringPiece signature_header_field = data.substr(
0, prologue_fallback_url_and_after_.signature_header_field_length());
base::span<const uint8_t> cbor_header =
base::as_bytes(base::make_span(data.substr(
prologue_fallback_url_and_after_.signature_header_field_length(),
prologue_fallback_url_and_after_.cbor_header_length())));
envelope_ = SignedExchangeEnvelope::Parse(
*version_, prologue_fallback_url_and_after_.fallback_url(),
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 SignedExchangeLoadResult::kHeaderParseError;
}
if (reporter_) {
reporter_->set_inner_url(envelope_->request_url().url);
reporter_->set_cert_url(envelope_->signature().cert_url);
}
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(cert_fetcher_factory_);
const bool force_fetch = load_flags_ & net::LOAD_BYPASS_CACHE;
cert_fetch_start_time_ = base::TimeTicks::Now();
cert_fetcher_ = std::move(cert_fetcher_factory_)
->CreateFetcherAndStart(
cert_url, force_fetch,
base::BindOnce(&SignedExchangeHandler::OnCertReceived,
base::Unretained(this)),
devtools_proxy_.get(), reporter_);
state_ = State::kFetchingCertificate;
return SignedExchangeLoadResult::kSuccess;
}
void SignedExchangeHandler::RunErrorCallback(SignedExchangeLoadResult result,
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(result, error, GetFallbackUrl(), network::ResourceResponseHead(),
nullptr);
state_ = State::kHeadersCallbackCalled;
}
void SignedExchangeHandler::OnCertReceived(
SignedExchangeLoadResult result,
std::unique_ptr<SignedExchangeCertificateChain> cert_chain) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::OnCertReceived");
base::TimeDelta cert_fetch_duration =
base::TimeTicks::Now() - cert_fetch_start_time_;
DCHECK_EQ(state_, State::kFetchingCertificate);
if (result != SignedExchangeLoadResult::kSuccess) {
UMA_HISTOGRAM_MEDIUM_TIMES("SignedExchange.Time.CertificateFetch.Failure",
cert_fetch_duration);
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(), "Failed to fetch the certificate.",
std::make_pair(0 /* signature_index */,
SignedExchangeError::Field::kSignatureCertUrl));
RunErrorCallback(result, net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
UMA_HISTOGRAM_MEDIUM_TIMES("SignedExchange.Time.CertificateFetch.Success",
cert_fetch_duration);
unverified_cert_chain_ = std::move(cert_chain);
DCHECK(version_.has_value());
const SignedExchangeSignatureVerifier::Result verify_result =
SignedExchangeSignatureVerifier::Verify(
*version_, *envelope_, unverified_cert_chain_->cert(),
GetVerificationTime(), devtools_proxy_.get());
UMA_HISTOGRAM_ENUMERATION(kHistogramSignatureVerificationResult,
verify_result);
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(
signed_exchange_utils::GetLoadResultFromSignatureVerifierResult(
verify_result),
net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
auto certificate = unverified_cert_chain_->cert();
auto url = envelope_->request_url().url;
// 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();
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&VerifyCert, certificate, url, stapled_ocsp_response,
sct_list_from_cert_cbor, frame_tree_node_id_getter_,
base::BindOnce(&SignedExchangeHandler::OnVerifyCert,
weak_factory_.GetWeakPtr())));
}
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.
UMA_HISTOGRAM_ENUMERATION(kHistogramOCSPResponseStatus,
ocsp_result.response_status,
static_cast<base::HistogramBase::Sample>(
net::OCSPVerifyResult::RESPONSE_STATUS_MAX) +
1);
if (ocsp_result.response_status == net::OCSPVerifyResult::PROVIDED) {
UMA_HISTOGRAM_ENUMERATION(kHistogramOCSPRevocationStatus,
ocsp_result.revocation_status,
static_cast<base::HistogramBase::Sample>(
net::OCSPRevocationStatus::MAX_VALUE) +
1);
if (ocsp_result.revocation_status == net::OCSPRevocationStatus::GOOD)
return true;
}
return false;
}
void SignedExchangeHandler::OnVerifyCert(
int32_t error_code,
const net::CertVerifyResult& cv_result,
const net::ct::CTVerifyResult& ct_result) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::OnCertVerifyComplete");
// net::Error codes are negative, so we put - in front of it.
base::UmaHistogramSparse(kHistogramCertVerificationResult, -error_code);
UMA_HISTOGRAM_ENUMERATION(kHistogramCTVerificationResult,
ct_result.policy_compliance,
net::ct::CTPolicyCompliance::CT_POLICY_COUNT);
if (error_code != net::OK) {
SignedExchangeLoadResult result;
std::string error_message;
if (error_code == net::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED) {
error_message = base::StringPrintf(
"CT verification failed. result: %s, policy compliance: %d",
net::ErrorToShortString(error_code).c_str(),
ct_result.policy_compliance);
result = SignedExchangeLoadResult::kCTVerificationError;
} else {
error_message =
base::StringPrintf("Certificate verification error: %s",
net::ErrorToShortString(error_code).c_str());
result = SignedExchangeLoadResult::kCertVerificationError;
}
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(), error_message,
std::make_pair(0 /* signature_index */,
SignedExchangeError::Field::kSignatureCertUrl));
RunErrorCallback(result, net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
if (!CheckCertExtension(cv_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(SignedExchangeLoadResult::kCertRequirementsNotMet,
net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
if (!CheckOCSPStatus(cv_result.ocsp_result)) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy_.get(),
base::StringPrintf("OCSP check failed: %s",
OCSPErrorToString(cv_result.ocsp_result).c_str()),
std::make_pair(0 /* signature_index */,
SignedExchangeError::Field::kSignatureCertUrl));
RunErrorCallback(SignedExchangeLoadResult::kOCSPError,
net::ERR_INVALID_SIGNED_EXCHANGE);
return;
}
network::ResourceResponseHead response_head;
response_head.is_signed_exchange_inner_response = true;
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 digest_header_value;
response_head.headers->EnumerateHeader(nullptr, kDigestHeader,
&digest_header_value);
auto mi_stream = std::make_unique<MerkleIntegritySourceStream>(
digest_header_value, std::move(source_));
net::SSLInfo ssl_info;
ssl_info.cert = cv_result.verified_cert;
ssl_info.unverified_cert = unverified_cert_chain_->cert();
ssl_info.cert_status = cv_result.cert_status;
ssl_info.is_issued_by_known_root = cv_result.is_issued_by_known_root;
ssl_info.public_key_hashes = cv_result.public_key_hashes;
ssl_info.ocsp_result = cv_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_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(SignedExchangeLoadResult::kSuccess, net::OK,
envelope_->request_url().url, response_head, std::move(mi_stream));
state_ = State::kHeadersCallbackCalled;
}
} // namespace content