chromium/chromeos/ash/components/carrier_lock/psm_claim_verifier_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/psm_claim_verifier_impl.h"

#include "base/logging.h"
#include "base/values.h"
#include "google_apis/google_api_keys.h"
#include "url/gurl.h"

namespace psm_rlwe = private_membership::rlwe;

namespace ash::carrier_lock {

namespace {

// const values
constexpr size_t kMaxOprfResponseSizeBytes = 1 << 20;   // 1MB;
constexpr size_t kMaxQueryResponseSizeBytes = 5 << 20;  // 5MB;
constexpr base::TimeDelta kRequestTimeoutSeconds = base::Seconds(60);

// PSM server URL
#define kPrivateSetBaseUrl "https://privatemembership-pa.googleapis.com"
const char kPrivateSetOprfUrl[] = kPrivateSetBaseUrl "/v1/membership:oprf";
const char kPrivateSetQueryUrl[] = kPrivateSetBaseUrl "/v1/membership:query";

// Traffic annotation for membership check
const net::NetworkTrafficAnnotationTag traffic_annotation =
    net::DefineNetworkTrafficAnnotation("carrier_lock_manager_check_membership",
                                        R"(
        semantics {
          sender: "Carrier Lock manager"
          description:
            "Check if the device is a member of Carrier Lock group on the "
            "Private Set Membership service."
          trigger: "Carrier Lock manager makes this network request at first "
                   "boot to check if the cellular modem should be locked."
          data: "Manufacturer, model, serial of the device and API key."
          destination: GOOGLE_OWNED_SERVICE
          internal {
            contacts {
                email: "[email protected]"
            }
          }
          user_data {
            type: DEVICE_ID
            type: HW_OS_INFO
          }
          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."
        })");

std::vector<psm_rlwe::RlwePlaintextId> GetPsmDeviceId(std::string serial,
                                                      std::string manufacturer,
                                                      std::string model) {
  std::string psm_id = base::JoinString({manufacturer, model, serial}, ",");

  psm_rlwe::RlwePlaintextId psm_rlwe_id;
  psm_rlwe_id.set_sensitive_id(psm_id);
  psm_rlwe_id.set_non_sensitive_id(psm_id);

  return {psm_rlwe_id};
}

}  // namespace

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

PsmClaimVerifierImpl::~PsmClaimVerifierImpl() = default;

void PsmClaimVerifierImpl::CheckPsmClaim(std::string serial,
                                         std::string manufacturer,
                                         std::string model,
                                         Callback callback) {
  ::rlwe::StatusOr<std::unique_ptr<psm_rlwe::PrivateMembershipRlweClient>>
      client_or_status;

  if (claim_callback_) {
    LOG(ERROR) << "PsmClaimVerifierImpl cannot handle multiple requests.";
    std::move(callback).Run(Result::kHandlerBusy);
    return;
  }

  std::vector<psm_rlwe::RlwePlaintextId> psm_rlwe_ids =
      GetPsmDeviceId(serial, manufacturer, model);
  api_key_ = google_apis::GetAPIKey();
  claim_callback_ = std::move(callback);

  if (!is_testing()) {
    client_or_status = psm_rlwe::PrivateMembershipRlweClient::Create(
        psm_rlwe::RlweUseCase::CROS_SIM_LOCK, psm_rlwe_ids);
  } else {
    client_or_status = psm_rlwe::PrivateMembershipRlweClient::CreateForTesting(
        psm_rlwe::RlweUseCase::CROS_SIM_LOCK, psm_rlwe_ids, "ec_cipher_key",
        "seed4567890123456789012345678912");
  }

  if (!client_or_status.ok()) {
    LOG(ERROR) << "Failed to create PSM client";
    ReturnError(Result::kCreatePsmClientFailed);
    return;
  }
  psm_client_ = std::move(client_or_status.value());

  SendOprfRequest();
}

bool PsmClaimVerifierImpl::GetMembership() {
  return membership_response_.is_member();
}

void PsmClaimVerifierImpl::SetupUrlLoader(std::string& request,
                                          const char* url) {
  auto resource_request = std::make_unique<network::ResourceRequest>();
  resource_request->url = GURL(url);
  resource_request->method = net::HttpRequestHeaders::kPostMethod;
  resource_request->headers.SetHeader("x-goog-api-key", api_key_);
  resource_request->headers.SetHeader(net::HttpRequestHeaders::kContentType,
                                      "application/x-protobuf");

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

  simple_url_loader_->AttachStringForUpload(request, "application/x-protobuf");
  simple_url_loader_->SetTimeoutDuration(kRequestTimeoutSeconds);
  simple_url_loader_->SetAllowHttpErrorResults(true);
}

void PsmClaimVerifierImpl::SendOprfRequest() {
  ::rlwe::StatusOr<psm_rlwe::PrivateMembershipRlweOprfRequest>
      oprf_request_or_status = psm_client_->CreateOprfRequest();
  if (!oprf_request_or_status.ok()) {
    LOG(ERROR) << "Failed to create PSM OPRF request";
    ReturnError(Result::kCreateOprfRequestFailed);
    return;
  }

  psm_rlwe::PrivateMembershipRlweOprfRequest oprf_request =
      oprf_request_or_status.value();

  std::string request_body;
  oprf_request.SerializeToString(&request_body);

  SetupUrlLoader(request_body, kPrivateSetOprfUrl);
  simple_url_loader_->DownloadToString(
      url_loader_factory_.get(),
      base::BindOnce(&PsmClaimVerifierImpl::OnCheckMembershipOprfDone,
                     weak_ptr_factory_.GetWeakPtr()),
      kMaxOprfResponseSizeBytes);
}

void PsmClaimVerifierImpl::OnCheckMembershipOprfDone(
    std::unique_ptr<std::string> response_body) {
  simple_url_loader_.reset();

  if (!response_body) {
    LOG(ERROR) << "Error in response to PSM OPRF request";
    ReturnError(Result::kConnectionError);
    return;
  }

  psm_rlwe::PrivateMembershipRlweOprfResponse psm_oprf_response;
  if (!psm_oprf_response.ParseFromString(*response_body)) {
    LOG(ERROR) << "Failed to parse PSM OPRF response";
    ReturnError(Result::kInvalidOprfReply);
    return;
  }

  // Generate PSM Query request body.
  const ::rlwe::StatusOr<psm_rlwe::PrivateMembershipRlweQueryRequest>
      query_request_or_status =
          psm_client_->CreateQueryRequest(psm_oprf_response);
  if (!query_request_or_status.ok()) {
    LOG(ERROR) << "Failed to create PSM Query request"
               << query_request_or_status.status();
    ReturnError(Result::kCreateQueryRequestFailed);
    return;
  }

  psm_rlwe::PrivateMembershipRlweQueryRequest query_request =
      query_request_or_status.value();

  std::string request_body;
  query_request.SerializeToString(&request_body);

  SetupUrlLoader(request_body, kPrivateSetQueryUrl);
  simple_url_loader_->DownloadToString(
      url_loader_factory_.get(),
      base::BindOnce(&PsmClaimVerifierImpl::OnCheckMembershipQueryDone,
                     weak_ptr_factory_.GetWeakPtr()),
      kMaxQueryResponseSizeBytes);
}

void PsmClaimVerifierImpl::OnCheckMembershipQueryDone(
    std::unique_ptr<std::string> response_body) {
  simple_url_loader_.reset();

  if (!response_body) {
    LOG(ERROR) << "Error in response to PSM Query request";
    ReturnError(Result::kConnectionError);
    return;
  }

  psm_rlwe::PrivateMembershipRlweQueryResponse psm_query_response;
  if (!psm_query_response.ParseFromString(*response_body)) {
    LOG(ERROR) << "Failed to parse PSM Query response";
    ReturnError(Result::kInvalidQueryReply);
    return;
  }

  ::rlwe::StatusOr<psm_rlwe::RlweMembershipResponses> response_or_status =
      psm_client_->ProcessQueryResponse(psm_query_response);
  if (!response_or_status.ok()) {
    LOG(ERROR) << "Failed to process PSM Query response";
    ReturnError(Result::kInvalidQueryReply);
    return;
  }

  // Ensure the existence of one membership response. Then, verify that it is
  // regarding the current PSM ID.
  psm_rlwe::RlweMembershipResponses rlwe_membership_responses =
      response_or_status.value();
  if (rlwe_membership_responses.membership_responses_size() != 1) {
    LOG(ERROR) << "Invalid membership response from PSM server";
    ReturnError(Result::kInvalidResponse);
    return;
  }

  membership_response_ =
      rlwe_membership_responses.membership_responses(0).membership_response();

  ReturnSuccess();
}

void PsmClaimVerifierImpl::ReturnError(Result err) {
  std::move(claim_callback_).Run(err);
}

void PsmClaimVerifierImpl::ReturnSuccess() {
  std::move(claim_callback_).Run(Result::kSuccess);
}

}  // namespace ash::carrier_lock