// Copyright 2018 The Chromium Authors
// 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 <memory>
#include <optional>
#include <string_view>
#include <utility>

#include "base/byte_size.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/string_view_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.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/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/web_package/prefetched_signed_exchange_cache_entry.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 "crypto/hash.h"
#include "ipc/constants.mojom.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "net/base/host_port_pair.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/cookies/cookie_setting_override.h"
#include "net/filter/source_stream.h"
#include "net/net_buildflags.h"
#include "net/ssl/ssl_info.h"
#include "net/storage_access_api/status.h"
#include "services/network/public/cpp/features.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"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
#include "third_party/blink/public/common/web_package/web_package_request_matcher.h"

namespace content {

namespace {

constexpr char kDigestHeader[] = "digest";
constexpr char kHistogramSignatureVerificationResult[] =
    "SignedExchange.SignatureVerificationResult";
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;
bool g_should_ignore_cert_validity_period_error = false;

bool IsSupportedSignedExchangeVersion(
    const std::optional<SignedExchangeVersion>& version) {
  return version == SignedExchangeVersion::kB3;
}

using VerifyCallback =
    base::OnceCallback<void(int32_t, const net::CertVerifyResult&, bool)>;

void VerifyCert(const scoped_refptr<net::X509Certificate>& certificate,
                const GURL& url,
                const std::string& ocsp_response,
                const std::string& sct_list,
                FrameTreeNodeId frame_tree_node_id,
                VerifyCallback callback) {
  VerifyCallback wrapped_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
      std::move(callback), net::ERR_FAILED, net::CertVerifyResult(), false);

  network::mojom::NetworkContext* network_context =
      g_network_context_for_testing;
  if (!network_context) {
    auto* frame = FrameTreeNode::GloballyFindByID(frame_tree_node_id);
    if (!frame)
      return;

    network_context = frame->current_frame_host()
                          ->GetProcess()
                          ->GetStoragePartition()
                          ->GetNetworkContext();
  }

  network_context->VerifyCertForSignedExchange(
      certificate, net::HostPortPair::FromURL(url), ocsp_response, sct_list,
      std::move(wrapped_callback));
}

std::string OCSPErrorToString(const bssl::OCSPVerifyResult& ocsp_result) {
  switch (ocsp_result.response_status) {
    case bssl::OCSPVerifyResult::PROVIDED:
      break;
    case bssl::OCSPVerifyResult::NOT_CHECKED:
      // This happens only in tests.
      return "OCSP verification was not performed.";
    case bssl::OCSPVerifyResult::MISSING:
      return "No OCSP Response was stapled.";
    case bssl::OCSPVerifyResult::ERROR_RESPONSE:
      return "OCSP response did not have a SUCCESSFUL status.";
    case bssl::OCSPVerifyResult::BAD_PRODUCED_AT:
      return "OCSP Response was produced at outside the certificate "
             "validity period.";
    case bssl::OCSPVerifyResult::NO_MATCHING_RESPONSE:
      return "OCSP Response did not match the certificate.";
    case bssl::OCSPVerifyResult::INVALID_DATE:
      return "OCSP Response was expired or not yet valid.";
    case bssl::OCSPVerifyResult::PARSE_RESPONSE_ERROR:
      return "OCSPResponse structure could not be parsed.";
    case bssl::OCSPVerifyResult::PARSE_RESPONSE_DATA_ERROR:
      return "OCSP ResponseData structure could not be parsed.";
    case bssl::OCSPVerifyResult::UNHANDLED_CRITICAL_EXTENSION:
      return "OCSP Response contained unhandled critical extension.";
  }

  switch (ocsp_result.revocation_status) {
    case bssl::OCSPRevocationStatus::GOOD:
      NOTREACHED();
    case bssl::OCSPRevocationStatus::REVOKED:
      return "OCSP response indicates that the certificate is revoked.";
    case bssl::OCSPRevocationStatus::UNKNOWN:
      return "OCSP responder doesn't know about the certificate.";
  }
  NOTREACHED();
}

}  // namespace

// static
void SignedExchangeHandler::SetNetworkContextForTesting(
    network::mojom::NetworkContext* network_context) {
  g_network_context_for_testing = network_context;
}

// static
void SignedExchangeHandler::SetShouldIgnoreCertValidityPeriodErrorForTesting(
    bool ignore) {
  g_should_ignore_cert_validity_period_error = ignore;
}

SignedExchangeHandler::SignedExchangeHandler(
    bool is_secure_transport,
    bool has_nosniff,
    std::string_view content_type,
    std::unique_ptr<net::SourceStream> body,
    ExchangeHeadersCallback headers_callback,
    std::unique_ptr<SignedExchangeCertFetcherFactory> cert_fetcher_factory,
    std::optional<net::IsolationInfo> outer_request_isolation_info,
    int load_flags,
    const net::IPEndPoint& remote_endpoint,
    std::unique_ptr<blink::WebPackageRequestMatcher> request_matcher,
    std::unique_ptr<SignedExchangeDevToolsProxy> devtools_proxy,
    SignedExchangeReporter* reporter,
    FrameTreeNodeId frame_tree_node_id)
    : 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)),
      devtools_proxy_(std::move(devtools_proxy)),
      outer_request_isolation_info_(std::move(outer_request_isolation_info)),
      load_flags_(load_flags),
      remote_endpoint_(remote_endpoint),
      request_matcher_(std::move(request_matcher)),
      reporter_(reporter),
      frame_tree_node_id_(frame_tree_node_id) {
  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\"",
                           std::string(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::SequencedTaskRunner::GetCurrentDefault()->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) {}

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::IOBufferWithSize>(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::BindOnce(&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);
  exchange_header_length_ += base::ByteSize(base::as_unsigned(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::SequencedTaskRunner::GetCurrentDefault()->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(
          header_buf_->first(
              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(
          header_buf_->first(
              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::span<const uint8_t> data = header_buf_->span();
  std::string_view signature_header_field = base::as_string_view(data.first(
      prologue_fallback_url_and_after_.signature_header_field_length()));
  base::span<const uint8_t> cbor_header = data.subspan(
      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(crbug.com/40565993): 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());

  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>(),
        std::nullopt);
  }
  std::move(headers_callback_)
      .Run(result, error, GetFallbackUrl(), nullptr, nullptr);
  state_ = State::kHeadersCallbackCalled;
}

void SignedExchangeHandler::OnCertReceived(
    SignedExchangeLoadResult result,
    std::unique_ptr<SignedExchangeCertificateChain> cert_chain,
    net::IPAddress cert_server_ip_address) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
               "SignedExchangeHandler::OnCertReceived");
  DCHECK_EQ(state_, State::kFetchingCertificate);

  base::TimeDelta cert_fetch_duration =
      base::TimeTicks::Now() - cert_fetch_start_time_;
  cert_server_ip_address_ = cert_server_ip_address;
  if (reporter_)
    reporter_->set_cert_server_ip_address(cert_server_ip_address_);

  if (result != SignedExchangeLoadResult::kSuccess) {
    DEPRECATED_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;
  }

  DEPRECATED_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_.get(),
          signed_exchange_utils::GetVerificationTime(), devtools_proxy_.get());
  UMA_HISTOGRAM_ENUMERATION(kHistogramSignatureVerificationResult,
                            verify_result);
  if (verify_result != SignedExchangeSignatureVerifier::Result::kSuccess) {
    std::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 ? std::make_optional(
                          std::make_pair(0 /* signature_index */, *error_field))
                    : std::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();

  VerifyCert(certificate, url, stapled_ocsp_response, sct_list_from_cert_cbor,
             frame_tree_node_id_,
             base::BindOnce(&SignedExchangeHandler::OnVerifyCert,
                            weak_factory_.GetWeakPtr()));
}

// https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#cross-origin-cert-req
SignedExchangeLoadResult SignedExchangeHandler::CheckCertRequirements(
    const net::X509Certificate* verified_cert) {
  // 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]
  if (!net::asn1::HasCanSignHttpExchangesDraftExtension(
          net::x509_util::CryptoBufferAsStringPiece(
              verified_cert->cert_buffer())) &&
      !unverified_cert_chain_->ShouldIgnoreErrors()) {
    signed_exchange_utils::ReportErrorAndTraceEvent(
        devtools_proxy_.get(),
        "Certificate must have CanSignHttpExchangesDraft extension.",
        std::make_pair(0 /* signature_index */,
                       SignedExchangeError::Field::kSignatureCertUrl));
    return SignedExchangeLoadResult::kCertRequirementsNotMet;
  }

  // - After 2019-08-01, clients MUST reject all certificates with this
  // extension that have a Validity Period longer than 90 days. [spec text]
  base::TimeDelta validity_period =
      verified_cert->valid_expiry() - verified_cert->valid_start();
  if (validity_period > base::Days(90) &&
      !unverified_cert_chain_->ShouldIgnoreErrors() &&
      !g_should_ignore_cert_validity_period_error) {
    signed_exchange_utils::ReportErrorAndTraceEvent(
        devtools_proxy_.get(),
        "After 2019-08-01, Signed Exchange's certificate must not have a "
        "validity period longer than 90 days.",
        std::make_pair(0 /* signature_index */,
                       SignedExchangeError::Field::kSignatureCertUrl));
    return SignedExchangeLoadResult::kCertValidityPeriodTooLong;
  }
  return SignedExchangeLoadResult::kSuccess;
}

bool SignedExchangeHandler::CheckOCSPStatus(
    const bssl::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::Sample32>(
                                bssl::OCSPVerifyResult::RESPONSE_STATUS_MAX) +
                                1);
  if (ocsp_result.response_status == bssl::OCSPVerifyResult::PROVIDED) {
    UMA_HISTOGRAM_ENUMERATION(kHistogramOCSPRevocationStatus,
                              ocsp_result.revocation_status,
                              static_cast<base::HistogramBase::Sample32>(
                                  bssl::OCSPRevocationStatus::MAX_VALUE) +
                                  1);
    if (ocsp_result.revocation_status == bssl::OCSPRevocationStatus::GOOD) {
      return true;
    }
  }
  return false;
}

void SignedExchangeHandler::OnVerifyCert(int32_t error_code,
                                         const net::CertVerifyResult& cv_result,
                                         bool pkp_bypassed) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
               "SignedExchangeHandler::OnCertVerifyComplete");
  UMA_HISTOGRAM_ENUMERATION(kHistogramCTVerificationResult,
                            cv_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(),
          static_cast<int>(cv_result.policy_compliance));
      result = SignedExchangeLoadResult::kCTVerificationError;
    } else {
      error_message =
          base::StringPrintf("Certificate verification error: %s",
                             net::ErrorToShortString(error_code).c_str());
      if (error_code == net::ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN)
        result = SignedExchangeLoadResult::kPKPViolationError;
      else
        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;
  }

  SignedExchangeLoadResult result =
      CheckCertRequirements(cv_result.verified_cert.get());
  if (result != SignedExchangeLoadResult::kSuccess) {
    RunErrorCallback(result, 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;
  }

  auto response_head = network::mojom::URLResponseHead::New();
  response_head->is_signed_exchange_inner_response = true;

  response_head->headers = envelope_->BuildHttpResponseHeaders();
  response_head->headers->GetMimeTypeAndCharset(&response_head->mime_type,
                                                &response_head->charset);

  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.pkp_bypassed = pkp_bypassed;
  ssl_info.public_key_hashes = cv_result.public_key_hashes;
  ssl_info.is_fatal_cert_error = net::IsCertStatusError(ssl_info.cert_status);
  ssl_info.signed_certificate_timestamps = cv_result.scts;
  ssl_info.ct_policy_compliance = cv_result.policy_compliance;
#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
  ssl_info.crs_root_id = cv_result.crs_root_id;
#endif
  response_head->ssl_info = std::move(ssl_info);

  if (!request_matcher_->MatchRequest(envelope_->response_headers())) {
    signed_exchange_utils::ReportErrorAndTraceEvent(
        devtools_proxy_.get(),
        "Signed Exchange's Variants / Variant-Key don't match the request.");
    RunErrorCallback(SignedExchangeLoadResult::kVariantMismatch,
                     net::ERR_INVALID_SIGNED_EXCHANGE);
    return;
  }

  // For prefetch, cookies will be checked by PrefetchedSignedExchangeCache.
  if (!(load_flags_ & net::LOAD_PREFETCH) &&
      signed_exchange_utils::IsCookielessOnlyExchange(
          *response_head->headers)) {
    CheckAbsenceOfCookies(base::BindOnce(&SignedExchangeHandler::CreateResponse,
                                         weak_factory_.GetWeakPtr(),
                                         std::move(response_head)));
    return;
  }
  CreateResponse(std::move(response_head));
}

void SignedExchangeHandler::CheckAbsenceOfCookies(base::OnceClosure callback) {
  auto* frame = FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
  if (!frame) {
    std::move(callback).Run();
    return;
  }
  CHECK(outer_request_isolation_info_.has_value());

  StoragePartition* storage_partition =
      frame->current_frame_host()->GetProcess()->GetStoragePartition();
  url::Origin inner_url_origin =
      url::Origin::Create(envelope_->request_url().url);
  net::IsolationInfo isolation_info =
      outer_request_isolation_info_->CreateForRedirect(inner_url_origin);

  RenderFrameHostImpl* render_frame_host = frame->current_frame_host();
  static_cast<StoragePartitionImpl*>(storage_partition)
      ->CreateRestrictedCookieManager(
          network::mojom::RestrictedCookieManagerRole::NETWORK,
          inner_url_origin, isolation_info,
          /* is_service_worker = */ false,
          render_frame_host ? render_frame_host->GetProcess()->GetDeprecatedID()
                            : -1,
          render_frame_host ? render_frame_host->GetRoutingID()
                            : IPC::mojom::kRoutingIdNone,
          /*prefer_bound_cookie_context=*/false,
          /*cookie_setting_overrides=*/
          render_frame_host ? render_frame_host->GetCookieSettingOverrides()
                            : net::CookieSettingOverrides(),
          /*devtools_cookie_setting_overrides=*/net::CookieSettingOverrides(),
          cookie_manager_.BindNewPipeAndPassReceiver(),
          render_frame_host ? render_frame_host->CreateCookieAccessObserver(
                                  CookieAccessDetails::Source::kNonNavigation)
                            : mojo::NullRemote());

  CHECK(isolation_info.top_frame_origin().has_value());
  auto match_options = network::mojom::CookieManagerGetOptions::New();
  match_options->name = "";
  match_options->match_type = network::mojom::CookieMatchType::STARTS_WITH;
  // We set `storage_access_api_status` to kAccessViaAPI below in order to use
  // any Storage Access grant that exists, since a frame might go through this
  // code path and then obtain storage access in order to access cookies. Using
  // true instead of false here means we are being conservative, since this runs
  // an error callback if any cookies were retrieved.
  cookie_manager_->GetAllForUrl(
      envelope_->request_url().url, isolation_info.site_for_cookies(),
      *isolation_info.top_frame_origin(),
      net::StorageAccessApiStatus::kAccessViaAPI, std::move(match_options),
      /*is_ad_tagged=*/false,
      /*apply_devtools_overrides=*/false,
      /*force_disable_third_party_cookies=*/false,
      base::BindOnce(&SignedExchangeHandler::OnGetCookies,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
}

void SignedExchangeHandler::OnGetCookies(
    base::OnceClosure callback,
    const std::vector<net::CookieWithAccessResult>& results) {
  if (!results.empty()) {
    RunErrorCallback(SignedExchangeLoadResult::kHadCookieForCookielessOnlySXG,
                     net::ERR_INVALID_SIGNED_EXCHANGE);
    return;
  }
  std::move(callback).Run();
}

void SignedExchangeHandler::CreateResponse(
    network::mojom::URLResponseHeadPtr response_head) {
  // TODO(crbug.com/40558902): 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::optional<base::ByteSize> content_length =
      response_head->headers->GetContentLength();
  response_head->content_length =
      content_length ? content_length->InBytes() : -1;
  response_head->remote_endpoint = remote_endpoint_;

  auto body_stream = CreateResponseBodyStream();
  if (!body_stream) {
    RunErrorCallback(SignedExchangeLoadResult::kInvalidIntegrityHeader,
                     net::ERR_INVALID_SIGNED_EXCHANGE);
    return;
  }

  if (devtools_proxy_) {
    devtools_proxy_->OnSignedExchangeReceived(
        envelope_, unverified_cert_chain_->cert(), response_head->ssl_info);
  }

  std::move(headers_callback_)
      .Run(SignedExchangeLoadResult::kSuccess, net::OK,
           envelope_->request_url().url, std::move(response_head),
           std::move(body_stream));
  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/41442806): 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::EqualsCaseInsensitiveASCII(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_));
}

bool SignedExchangeHandler::GetSignedExchangeInfoForPrefetchCache(
    PrefetchedSignedExchangeCacheEntry& entry) const {
  if (!envelope_)
    return false;
  entry.SetHeaderIntegrity(std::make_unique<net::SHA256HashValue>(
      envelope_->ComputeHeaderIntegrity()));
  entry.SetSignatureExpireTime(base::Time::UnixEpoch() +
                               base::Seconds(envelope_->signature().expires));
  entry.SetCertUrl(envelope_->signature().cert_url);
  entry.SetCertServerIPAddress(cert_server_ip_address_);
  return true;
}

}  // namespace content
