chromium/chromeos/ash/components/carrier_lock/provisioning_config_fetcher_impl.cc

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

#include "chromeos/ash/components/carrier_lock/provisioning_config_fetcher_impl.h"

#include "base/base64.h"
#include "base/json/json_reader.h"
#include "base/json/json_string_value_serializer.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_runner.h"
#include "base/values.h"
#include "google_apis/google_api_keys.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "url/gurl.h"

namespace ash::carrier_lock {

namespace {

// const values
constexpr size_t kMaxProvisioningResponseSizeBytes = 1 << 20;  // 1MB;
constexpr base::TimeDelta kRequestTimeoutSeconds = base::Seconds(30);
const char kAndroidId[] = "123";

// Pixel provisioning server data
const char kProvisioningRecord[] = "deviceProvisioningRecord";
const char kProvisioningConfig[] = "deviceProvisioningConfig";

const char kProvisioningUrl[] =
    "https://afwprovisioning-pa.googleapis.com"
    "/v1/get_device_provisioning_record";

// Traffic annotation for configuration request
const net::NetworkTrafficAnnotationTag traffic_annotation =
    net::DefineNetworkTrafficAnnotation(
        "carrier_lock_manager_fetch_configuration",
        R"(
        semantics {
          sender: "Carrier Lock manager"
          description:
            "Request Carrier Lock configuration to setup modem locks."
          trigger: "Carrier Lock manager makes this network request every time "
                   "lock configuration needs to be updated on modem."
          data: "FCM token, manufacturer, model, serial number, IMEI, device id"
                "and API key."
          destination: GOOGLE_OWNED_SERVICE
          internal {
            contacts {
                email: "[email protected]"
            }
          }
          user_data {
            type: DEVICE_ID
            type: HW_OS_INFO
            type: ACCESS_TOKEN
          }
          last_reviewed: "2023-10-24"
        }
        policy {
          cookies_allowed: NO
          setting: "This feature cannot be disabled in settings."
          policy_exception_justification: "Carrier Lock is always enforced."
        })");

}  // namespace

ProvisioningConfigFetcherImpl::ProvisioningConfigFetcherImpl(
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
    : url_loader_factory_(std::move(url_loader_factory)) {}

ProvisioningConfigFetcherImpl::~ProvisioningConfigFetcherImpl() = default;

void ProvisioningConfigFetcherImpl::RequestConfig(
    const std::string& serial,
    const std::string& imei,
    const std::string& manufacturer,
    const std::string& model,
    const std::string& fcm_token,
    Callback callback) {
  if (config_callback_) {
    LOG(ERROR)
        << "ProvisioningConfigFetcherImpl cannot handle multiple requests.";
    std::move(callback).Run(Result::kHandlerBusy);
    return;
  }

  config_callback_ = std::move(callback);

  // Prepare message header
  auto resource_request = std::make_unique<network::ResourceRequest>();
  resource_request->url = GURL(kProvisioningUrl);
  resource_request->method = net::HttpRequestHeaders::kPostMethod;
  resource_request->headers.SetHeader("x-goog-api-key",
                                      google_apis::GetAPIKey());
  resource_request->headers.SetHeader(net::HttpRequestHeaders::kAccept,
                                      "application/json, */*;q=0.5");
  resource_request->headers.SetHeader(net::HttpRequestHeaders::kContentType,
                                      "application/json");

  // Prepare request body
  base::Value::Dict request;
  base::Value::Dict device;
  std::string request_body;
  request.Set("android_id", kAndroidId);
  request.Set("gcm_registration_id", fcm_token);
  device.Set("manufacturer", manufacturer);
  device.Set("model", model);
  device.Set("serialNumber", serial);
  device.Set("chromeOsAttestedDeviceId", serial);
  device.Set("imei", imei);
  request.Set("deviceIdentifier", std::move(device));
  base::JSONWriter::Write(request, &request_body);

  // Send message using URLLoader
  simple_url_loader_ = network::SimpleURLLoader::Create(
      std::move(resource_request), traffic_annotation);
  if (!simple_url_loader_ || !url_loader_factory_) {
    LOG(ERROR) << "Failed to create URL loader";
    ReturnError(Result::kInitializationFailed);
    return;
  }

  simple_url_loader_->AttachStringForUpload(request_body, "application/json");
  simple_url_loader_->SetTimeoutDuration(kRequestTimeoutSeconds);
  simple_url_loader_->DownloadToString(
      url_loader_factory_.get(),
      base::BindOnce(&ProvisioningConfigFetcherImpl::OnDownloadToStringComplete,
                     base::Unretained(this)),
      kMaxProvisioningResponseSizeBytes);
}

std::string ProvisioningConfigFetcherImpl::GetFcmTopic() {
  return provision_config_.sim_lock_config().gcm_topic();
}

std::string ProvisioningConfigFetcherImpl::GetSignedConfig() {
  return provision_config_.sim_lock_config().signed_configuration();
}

::carrier_lock::CarrierRestrictionsMode
ProvisioningConfigFetcherImpl::GetRestrictionMode() {
  return provision_config_.sim_lock_config().carrier_restrictions_mode();
}

RestrictedNetworks ProvisioningConfigFetcherImpl::GetNumberOfNetworks() {
  RestrictedNetworks result;
  result.allowed = provision_config_.sim_lock_config().allowed_networks_size();
  result.disallowed =
      provision_config_.sim_lock_config().disallowed_networks_size();
  return result;
}

void ProvisioningConfigFetcherImpl::OnDownloadToStringComplete(
    std::unique_ptr<std::string> response_body) {
  simple_url_loader_.reset();
  if (!response_body) {
    LOG(ERROR) << "Provisioning response body is empty";
    ReturnError(Result::kConnectionError);
    return;
  }

  base::JSONReader::Result response_result =
      base::JSONReader::ReadAndReturnValueWithError(*response_body);
  if (!response_result.has_value()) {
    LOG(ERROR) << "Provisioning JSONReader failed: "
               << response_result.error().message;
    ReturnError(Result::kInvalidResponse);
    return;
  }

  if (!response_result.value().is_dict()) {
    LOG(ERROR) << "Provisioning response has unexpected type : "
               << base::StringPrintf("%u", static_cast<unsigned int>(
                                               response_result.value().type()));
    ReturnError(Result::kInvalidResponse);
    return;
  }
  base::Value::Dict response_value =
      std::move(response_result.value().GetDict());

  base::Value::Dict* response_record =
      response_value.FindDict(kProvisioningRecord);
  if (!response_record) {
    LOG(ERROR) << "Provisioning record not found";
    ReturnError(Result::kNoLockConfiguration);
    return;
  }

  base::Value* response_config = response_record->Find(kProvisioningConfig);
  if (!response_config || !response_config->is_string()) {
    LOG(ERROR) << "Provisioning config not found or not a string";
    ReturnError(Result::kNoLockConfiguration);
    return;
  }

  std::string response_string;
  if (!base::Base64Decode(response_config->GetString(), &response_string)) {
    LOG(ERROR) << "Provisioning config decoding failed";
    ReturnError(Result::kInvalidConfiguration);
    return;
  }

  if (!provision_config_.ParseFromString(response_string)) {
    LOG(ERROR) << "Provisioning device config parse failed";
    ReturnError(Result::kInvalidConfiguration);
    return;
  }

  std::move(config_callback_).Run(Result::kSuccess);
}

void ProvisioningConfigFetcherImpl::ReturnError(Result err) {
  std::move(config_callback_).Run(err);
}

}  // namespace ash::carrier_lock