chromium/chrome/browser/ash/attestation/attestation_ca_client.cc

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/attestation/attestation_ca_client.h"

#include <string>

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chromeos/dbus/constants/dbus_switches.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/network_isolation_key.h"
#include "net/http/http_status_code.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/proxy_lookup_client.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "url/gurl.h"

namespace {
// Values for the attestation server switch.
const char kAttestationServerDefault[] = "default";
const char kAttestationServerTest[] = "test";

// Endpoints for the default Google Privacy CA operations.
const char kDefaultEnrollRequestURL[] =
    "https://chromeos-ca.gstatic.com/enroll";
const char kDefaultCertificateRequestURL[] =
    "https://chromeos-ca.gstatic.com/sign";

// Endpoints for the test Google Privacy CA operations.
const char kTestEnrollRequestURL[] =
    "https://asbestos-qa.corp.google.com/enroll";
const char kTestCertificateRequestURL[] =
    "https://asbestos-qa.corp.google.com/sign";

const char kMimeContentType[] = "application/octet-stream";

}  // namespace

namespace ash {
namespace attestation {

namespace {

class CAProxyLookupClient : public network::mojom::ProxyLookupClient {
 public:
  // Not copyable nor movable.
  CAProxyLookupClient(const CAProxyLookupClient&) = delete;
  CAProxyLookupClient& operator=(const CAProxyLookupClient&) = delete;
  CAProxyLookupClient(CAProxyLookupClient&&) = delete;
  CAProxyLookupClient& operator=(CAProxyLookupClient&&) = delete;

  static void LookUpProxyForURL(
      network::mojom::NetworkContext* network_context,
      const GURL& url,
      AttestationCAClient::ProxyPresenceCallback callback) {
    // The created object will be deleted in `OnProxyLookupComplete()`.
    CAProxyLookupClient* client =
        new CAProxyLookupClient(network_context, url, std::move(callback));
    client->Start(network_context, url);
  }

  // network::mojom::ProxyLookupClient:
  void OnProxyLookupComplete(
      int32_t net_error,
      const std::optional<net::ProxyInfo>& proxy_info) override {
    LOG_IF(WARNING, !proxy_info.has_value())
        << " Error determining the proxy information: " << net_error;
    // Assume there is a proxy if failing to get proxy information.
    const bool has_proxy = !proxy_info.has_value() || !proxy_info->is_direct();
    receiver_.reset();
    std::move(callback_).Run(has_proxy);
    delete this;
  }

 private:
  CAProxyLookupClient(network::mojom::NetworkContext* network_context,
                      const GURL& url,
                      AttestationCAClient::ProxyPresenceCallback callback)
      : callback_(std::move(callback)) {
    CHECK(network_context);
  }
  void Start(network::mojom::NetworkContext* network_context, const GURL& url) {
    const net::NetworkAnonymizationKey network_anonymization_key =
        net::NetworkAnonymizationKey::CreateTransient();
    mojo::PendingRemote<network::mojom::ProxyLookupClient> proxy_lookup_client =
        receiver_.BindNewPipeAndPassRemote();
    receiver_.set_disconnect_handler(
        base::BindOnce(&CAProxyLookupClient::OnProxyLookupComplete,
                       base::Unretained(this), net::ERR_ABORTED, std::nullopt));

    network_context->LookUpProxyForURL(url, network_anonymization_key,
                                       std::move(proxy_lookup_client));
  }

  AttestationCAClient::ProxyPresenceCallback callback_;

  mojo::Receiver<network::mojom::ProxyLookupClient> receiver_{this};
};

}  // namespace

static PrivacyCAType GetAttestationServerType() {
  std::string value =
      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
          chromeos::switches::kAttestationServer);
  if (value.empty() || value == kAttestationServerDefault) {
    return DEFAULT_PCA;
  }
  if (value == kAttestationServerTest) {
    return TEST_PCA;
  }
  LOG(WARNING) << "Invalid attestation server value: " << value
               << ". Using default.";
  return DEFAULT_PCA;
}

AttestationCAClient::AttestationCAClient() {
  pca_type_ = GetAttestationServerType();
}

AttestationCAClient::~AttestationCAClient() {}

void AttestationCAClient::SendEnrollRequest(const std::string& request,
                                            DataCallback on_response) {
  FetchURL(
      GetType() == TEST_PCA ? kTestEnrollRequestURL : kDefaultEnrollRequestURL,
      request, std::move(on_response));
}

void AttestationCAClient::SendCertificateRequest(const std::string& request,
                                                 DataCallback on_response) {
  FetchURL(GetType() == TEST_PCA ? kTestCertificateRequestURL
                                 : kDefaultCertificateRequestURL,
           request, std::move(on_response));
}

void AttestationCAClient::OnURLLoadComplete(
    std::list<std::unique_ptr<network::SimpleURLLoader>>::iterator it,
    DataCallback callback,
    std::unique_ptr<std::string> response_body) {
  // Move the loader out of the active loaders list.
  std::unique_ptr<network::SimpleURLLoader> url_loader = std::move(*it);
  url_loaders_.erase(it);

  DCHECK(url_loader);

  if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers) {
    int response_code = url_loader->ResponseInfo()->headers->response_code();

    if (response_code < 200 || response_code > 299) {
      LOG(ERROR) << "Attestation CA sent an HTTP error response: "
                 << response_code;
      std::move(callback).Run(false, "");
      return;
    }
  }

  if (!response_body) {
    int net_error = url_loader->NetError();
    LOG(ERROR) << "Attestation CA request failed, error: "
               << net::ErrorToString(net_error);
    std::move(callback).Run(false, "");
    return;
  }

  // Run the callback last because it may delete |this|.
  std::move(callback).Run(true, *response_body);
}

void AttestationCAClient::FetchURL(const std::string& url,
                                   const std::string& request,
                                   DataCallback on_response) {
  const net::NetworkTrafficAnnotationTag traffic_annotation =
      net::DefineNetworkTrafficAnnotation("attestation_ca_client", R"(
        semantics {
          sender: "Attestation CA client"
          description:
            "Sends requests to the Attestation CA as part of the remote "
            "attestation feature, such as enrolling for remote attestation or "
            "to obtain an attestation certificate."
          trigger:
            "Device enrollment, content protection or get an attestation "
            "certificate for a hardware-protected key."
          data:
            "The data from AttestationCertificateRequest or from "
            "AttestationEnrollmentRequest message from "
            "cryptohome/attestation.proto. Some of the important data being "
            "encrypted endorsement certificate, attestation identity public "
            "key, PCR0 and PCR1 TPM values."
          destination: GOOGLE_OWNED_SERVICE
        }
        policy {
          cookies_allowed: NO
          setting:
            "The device setting AttestationForContentProtectionEnabled "
            "can disable the attestation for content protection."
          policy_exception_justification: "Not implemented."
        })");
  auto resource_request = std::make_unique<network::ResourceRequest>();
  resource_request->url = GURL(url);
  resource_request->method = "POST";
  resource_request->load_flags = net::LOAD_DISABLE_CACHE;
  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;

  auto url_loader = network::SimpleURLLoader::Create(
      std::move(resource_request), traffic_annotation);
  url_loader->AttachStringForUpload(request, kMimeContentType);

  auto* raw_url_loader = url_loader.get();
  url_loaders_.push_back(std::move(url_loader));

  raw_url_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
      g_browser_process->shared_url_loader_factory().get(),
      base::BindOnce(&AttestationCAClient::OnURLLoadComplete,
                     base::Unretained(this), std::move(--url_loaders_.end()),
                     std::move(on_response)));
}

PrivacyCAType AttestationCAClient::GetType() {
  return pca_type_;
}

void AttestationCAClient::CheckIfAnyProxyPresent(
    ProxyPresenceCallback callback) {
  GURL url(GetType() == TEST_PCA ? kTestEnrollRequestURL
                                 : kDefaultEnrollRequestURL);
  DCHECK(url.is_valid());

  network::mojom::NetworkContext* network_context = nullptr;
  // Uses the injected network context if present.
  if (network_context_for_testing_ != nullptr) {
    network_context = network_context_for_testing_;
  } else if (!g_browser_process ||
             !g_browser_process->system_network_context_manager() ||
             !g_browser_process->system_network_context_manager()
                  ->GetContext()) {
    LOG(DFATAL) << "No valid system network context.";
    std::move(callback).Run(/*is_any_proxy_present=*/true);
    return;
  } else {
    network_context =
        g_browser_process->system_network_context_manager()->GetContext();
  }

  CAProxyLookupClient::LookUpProxyForURL(
      network_context, url.DeprecatedGetOriginAsURL(), std::move(callback));
}

}  // namespace attestation
}  // namespace ash