blob: 0b5927ef0ba60d6075fd0d38795738ef35c6c042 [file] [log] [blame]
// Copyright 2019 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_reporter.h"
#include <string>
#include <utility>
#include "base/callback.h"
#include "base/memory/ptr_util.h"
#include "base/task/post_task.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/web_package/signed_exchange_utils.h"
#include "content/public/browser/browser_context.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/site_instance.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "net/base/ip_endpoint.h"
#include "services/network/public/cpp/resource_response_info.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "url/origin.h"
namespace content {
namespace {
constexpr char kSXGResultOk[] = "ok";
constexpr char kSXGResultFailed[] = "sxg.failed";
constexpr char kSXGResultMiError[] = "sxg.mi_error";
constexpr char kSXGResultNonSecureDistributor[] = "sxg.non_secure_distributor";
constexpr char kSXGResultParseError[] = "sxg.parse_error";
constexpr char kSXGResultInvalidIntegrityHeader[] =
"sxg.invalid_integrity_header";
constexpr char kSXGResultSignatureVerificationError[] =
"sxg.signature_verification_error";
constexpr char kSXGResultCertVerificationError[] =
"sxg.cert_verification_error";
constexpr char kSXGResultCertFetchError[] = "sxg.cert_fetch_error";
constexpr char kSXGResultCertParseError[] = "sxg.cert_parse_error";
constexpr char kSXGResultVariantMismatch[] = "sxg.variant_mismatch";
const char* GetResultTypeString(SignedExchangeLoadResult result) {
switch (result) {
case SignedExchangeLoadResult::kSuccess:
return kSXGResultOk;
case SignedExchangeLoadResult::kSXGServedFromNonHTTPS:
return kSXGResultNonSecureDistributor;
case SignedExchangeLoadResult::kFallbackURLParseError:
case SignedExchangeLoadResult::kVersionMismatch:
case SignedExchangeLoadResult::kHeaderParseError:
case SignedExchangeLoadResult::kSXGHeaderNetError:
return kSXGResultParseError;
case SignedExchangeLoadResult::kCertFetchError:
return kSXGResultCertFetchError;
case SignedExchangeLoadResult::kCertParseError:
return kSXGResultCertParseError;
case SignedExchangeLoadResult::kSignatureVerificationError:
return kSXGResultSignatureVerificationError;
case SignedExchangeLoadResult::kCertVerificationError:
return kSXGResultCertVerificationError;
case SignedExchangeLoadResult::kCTVerificationError:
return kSXGResultCertVerificationError;
case SignedExchangeLoadResult::kOCSPError:
return kSXGResultCertVerificationError;
case SignedExchangeLoadResult::kCertRequirementsNotMet:
case SignedExchangeLoadResult::kCertValidityPeriodTooLong:
return kSXGResultCertVerificationError;
case SignedExchangeLoadResult::kMerkleIntegrityError:
return kSXGResultMiError;
case SignedExchangeLoadResult::kSXGServedWithoutNosniff:
// TODO(crbug/910516): Need to update the spec to send the report in this
// case.
return kSXGResultParseError;
case SignedExchangeLoadResult::kInvalidIntegrityHeader:
return kSXGResultInvalidIntegrityHeader;
case SignedExchangeLoadResult::kVariantMismatch:
// TODO(crbug/910516): Need to update the spec to send the report in this
// case.
return kSXGResultVariantMismatch;
}
NOTREACHED();
return kSXGResultFailed;
}
bool IsCertRelatedErrorResult(const char* result_string) {
return result_string == kSXGResultSignatureVerificationError ||
result_string == kSXGResultCertVerificationError ||
result_string == kSXGResultCertFetchError ||
result_string == kSXGResultCertParseError;
}
// [spec text]
// - If report body’s "sxg"'s "cert_url"'s scheme is not "data" and report’s
// result is "signature_verification_error" or "cert_verification_error" or
// "cert_fetch_error" or "cert_parse_error":
// - If report’s outer request's url's origin is different from any origin
// of the URLs in report’s cert URL list, or report’s server IP is
// different from any of the IP address in report’s cert server IP list:
bool ShouldDowngradeReport(const char* result_string,
const network::mojom::SignedExchangeReport& report,
const net::IPAddress& cert_server_ip_address) {
if (report.cert_url.SchemeIs("data"))
return false;
if (!IsCertRelatedErrorResult(result_string))
return false;
if (url::Origin::Create(report.outer_url) !=
url::Origin::Create(report.cert_url)) {
return true;
}
if (!cert_server_ip_address.empty() &&
cert_server_ip_address != report.server_ip_address) {
return true;
}
return false;
}
void ReportResultOnUI(base::OnceCallback<int(void)> frame_tree_node_id_getter,
network::mojom::SignedExchangeReportPtr report) {
int frame_tree_node_id = std::move(frame_tree_node_id_getter).Run();
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id);
if (!frame_tree_node)
return;
RenderFrameHostImpl* frame_host = frame_tree_node->current_frame_host();
if (!frame_host)
return;
SiteInstance* site_instance = frame_host->GetSiteInstance();
DCHECK(site_instance);
WebContents* web_contents = WebContents::FromRenderFrameHost(frame_host);
if (!web_contents)
return;
StoragePartition* partition = BrowserContext::GetStoragePartition(
web_contents->GetBrowserContext(), site_instance);
DCHECK(partition);
partition->GetNetworkContext()->QueueSignedExchangeReport(std::move(report));
}
} // namespace
// static
std::unique_ptr<SignedExchangeReporter> SignedExchangeReporter::MaybeCreate(
const GURL& outer_url,
const std::string& referrer,
const network::ResourceResponseHead& response,
base::OnceCallback<int(void)> frame_tree_node_id_getter) {
if (!signed_exchange_utils::
IsSignedExchangeReportingForDistributorsEnabled()) {
return nullptr;
}
return base::WrapUnique(new SignedExchangeReporter(
outer_url, referrer, response, std::move(frame_tree_node_id_getter)));
}
SignedExchangeReporter::SignedExchangeReporter(
const GURL& outer_url,
const std::string& referrer,
const network::ResourceResponseHead& response,
base::OnceCallback<int(void)> frame_tree_node_id_getter)
: report_(network::mojom::SignedExchangeReport::New()),
request_start_(response.load_timing.request_start),
frame_tree_node_id_getter_(std::move(frame_tree_node_id_getter)) {
report_->outer_url = outer_url;
report_->referrer = referrer;
report_->server_ip_address = response.remote_endpoint.address();
// If we got response headers, assume that the connection used HTTP/1.1
// unless ALPN negotiation tells us otherwise (handled below).
report_->protocol = response.was_alpn_negotiated
? response.alpn_negotiated_protocol
: std::string("http/1.1");
report_->status_code =
response.headers ? response.headers->response_code() : 0;
report_->method = "GET";
}
SignedExchangeReporter::~SignedExchangeReporter() = default;
void SignedExchangeReporter::set_cert_server_ip_address(
const net::IPAddress& cert_server_ip_address) {
cert_server_ip_address_ = cert_server_ip_address;
}
void SignedExchangeReporter::set_inner_url(const GURL& inner_url) {
DCHECK(report_);
report_->inner_url = inner_url;
}
void SignedExchangeReporter::set_cert_url(const GURL& cert_url) {
DCHECK(report_);
report_->cert_url = cert_url;
}
void SignedExchangeReporter::ReportResultAndFinish(
SignedExchangeLoadResult result) {
DCHECK(report_);
DCHECK(frame_tree_node_id_getter_);
const char* result_string = GetResultTypeString(result);
report_->success = result == SignedExchangeLoadResult::kSuccess;
if (ShouldDowngradeReport(result_string, *report_, cert_server_ip_address_)) {
// If the report should be downgraded (See the comment of
// ShouldDowngradeReport):
// [spec text]
// - Set report body’s "type" to "sxg.failed".
// - Set report body’s "elapsed_time" to 0.
report_->type = kSXGResultFailed;
report_->elapsed_time = base::TimeDelta();
} else {
report_->type = result_string;
report_->elapsed_time = base::TimeTicks::Now() - request_start_;
}
base::PostTask(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&ReportResultOnUI, std::move(frame_tree_node_id_getter_),
std::move(report_)));
}
} // namespace content