chromium/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/mac_key_rotation_command.cc

// Copyright 2022 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/enterprise/connectors/device_trust/key_management/browser/commands/mac_key_rotation_command.h"

#include <string>
#include <utility>

#include "base/check.h"
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequence_checker.h"
#include "base/syslog_logging.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/enterprise/connectors/device_trust/common/device_trust_constants.h"
#include "chrome/browser/enterprise/connectors/device_trust/device_trust_features.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/network/key_network_delegate.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/network/mojo_key_network_delegate.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_types.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/installer/metrics_util.h"
#include "chrome/common/channel_info.h"
#include "components/enterprise/browser/controller/browser_dm_token_storage.h"
#include "components/enterprise/client_certificates/core/browser_cloud_management_delegate.h"
#include "components/enterprise/client_certificates/core/cloud_management_delegate.h"
#include "components/enterprise/client_certificates/core/dm_server_client.h"
#include "components/policy/core/common/cloud/device_management_service.h"
#include "components/version_info/channel.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "url/gurl.h"

namespace enterprise_connectors {

namespace {

constexpr char kStableChannelHostName[] = "m.google.com";

bool ValidRotationCommand(const std::string& host_name) {
  return chrome::GetChannel() != version_info::Channel::STABLE ||
         host_name == kStableChannelHostName;
}

// Allows the key rotation maanger to be released in the correct worker thread.
void OnBackgroundTearDown(
    std::unique_ptr<KeyRotationManager> key_rotation_manager,
    base::OnceCallback<void(KeyRotationResult)> result_callback,
    KeyRotationResult result) {
  std::move(result_callback).Run(result);
}

// Runs on the thread pool.
void StartRotation(
    const GURL& dm_server_url,
    const std::string& dm_token,
    const std::string& nonce,
    std::unique_ptr<network::PendingSharedURLLoaderFactory>
        pending_url_loader_factory,
    policy::BrowserDMTokenStorage* dm_token_storage,
    policy::DeviceManagementService* device_management_service,
    base::OnceCallback<void(KeyRotationResult)> result_callback) {
  // TODO(b/351201459): When DTCKeyRotationUploadedBySharedAPIEnabled is fully
  // launched, remove `dm_server_url` and `dm_token` from this function.

  CHECK(pending_url_loader_factory);

  std::unique_ptr<KeyRotationManager> key_rotation_manager = nullptr;
  if (IsDTCKeyRotationUploadedBySharedAPI()) {
    CHECK(dm_token_storage);
    CHECK(device_management_service);
    key_rotation_manager = KeyRotationManager::Create(
        std::make_unique<
            enterprise_attestation::BrowserCloudManagementDelegate>(
            dm_token_storage, enterprise_attestation::DMServerClient::Create(
                                  device_management_service,
                                  network::SharedURLLoaderFactory::Create(
                                      std::move(pending_url_loader_factory)))));

  } else {
    key_rotation_manager =
        KeyRotationManager::Create(std::make_unique<MojoKeyNetworkDelegate>(
            network::SharedURLLoaderFactory::Create(
                std::move(pending_url_loader_factory))));
  }

  CHECK(key_rotation_manager);

  auto* key_rotation_manager_ptr = key_rotation_manager.get();
  key_rotation_manager_ptr->Rotate(
      dm_server_url, dm_token, nonce,
      base::BindOnce(&OnBackgroundTearDown, std::move(key_rotation_manager),
                     std::move(result_callback)));
}

}  // namespace

MacKeyRotationCommand::MacKeyRotationCommand(
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
    : url_loader_factory_(std::move(url_loader_factory)),
      background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
      client_(SecureEnclaveClient::Create()) {
  CHECK(url_loader_factory_);
  CHECK(client_);
}

MacKeyRotationCommand::MacKeyRotationCommand(
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
    policy::BrowserDMTokenStorage* dm_token_storage,
    policy::DeviceManagementService* device_management_service)
    : url_loader_factory_(std::move(url_loader_factory)),
      dm_token_storage_(dm_token_storage),
      device_management_service_(device_management_service),
      background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
      client_(SecureEnclaveClient::Create()) {
  CHECK(IsDTCKeyRotationUploadedBySharedAPI());
  CHECK(dm_token_storage_);
  CHECK(device_management_service_);
  CHECK(url_loader_factory_);
  CHECK(client_);
}

MacKeyRotationCommand::~MacKeyRotationCommand() = default;

void MacKeyRotationCommand::Trigger(const KeyRotationCommand::Params& params,
                                    Callback callback) {
  // Used to ensure that this function is being called on the main thread.
  SEQUENCE_CHECKER(sequence_checker_);

  // Parallel usage of command objects is not supported.
  CHECK(!pending_callback_);

  if (!client_->VerifySecureEnclaveSupported()) {
    SYSLOG(ERROR) << "Device trust key rotation failed. The secure enclave is "
                     "not supported.";
    std::move(callback).Run(KeyRotationCommand::Status::FAILED_OS_RESTRICTION);
    return;
  }

  GURL dm_server_url(params.dm_server_url);
  // TODO(b/351201459): When IsDTCKeyRotationUploadedBySharedAPI is fully
  // launched, ignore `dm_server_url` and `dm_token`.
  if (!IsDTCKeyRotationUploadedBySharedAPI()) {
    if (!ValidRotationCommand(dm_server_url.host())) {
      SYSLOG(ERROR)
          << "Device trust key rotation failed. The server URL is invalid.";
      std::move(callback).Run(KeyRotationCommand::Status::FAILED);
      return;
    }
  }

  pending_callback_ = std::move(callback);

  timeout_timer_.Start(
      FROM_HERE, timeouts::kProcessWaitTimeout,
      base::BindOnce(&MacKeyRotationCommand::OnKeyRotationTimeout,
                     weak_factory_.GetWeakPtr()));

  auto rotation_result_callback =
      base::BindPostTaskToCurrentDefault(base::BindOnce(
          &MacKeyRotationCommand::OnKeyRotated, weak_factory_.GetWeakPtr()));

  if (IsDTCKeyRotationUploadedBySharedAPI()) {
    // Kicks off the key rotation process in a worker thread.
    background_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&StartRotation, dm_server_url, params.dm_token,
                       params.nonce, url_loader_factory_->Clone(),
                       dm_token_storage_, device_management_service_,
                       std::move(rotation_result_callback)));

    return;
  }

  // Kicks off the key rotation process in a worker thread.
  background_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&StartRotation, dm_server_url, params.dm_token,
                                params.nonce, url_loader_factory_->Clone(),
                                dm_token_storage_, device_management_service_,
                                std::move(rotation_result_callback)));
}

void MacKeyRotationCommand::OnKeyRotated(KeyRotationResult result) {
  // Used to ensure that this function is being called on the main thread.
  SEQUENCE_CHECKER(sequence_checker_);

  if (!pending_callback_) {
    // The callback may have already run in timeout cases.
    return;
  }

  timeout_timer_.Stop();

  auto response_status = KeyRotationCommand::Status::FAILED;
  switch (result) {
    case KeyRotationResult::kSucceeded:
      response_status = KeyRotationCommand::Status::SUCCEEDED;
      break;
    case KeyRotationResult::kFailed:
      SYSLOG(ERROR) << "Device trust key rotation failed.";
      response_status = KeyRotationCommand::Status::FAILED;
      break;
    case KeyRotationResult::kInsufficientPermissions:
      SYSLOG(ERROR) << "Device trust key rotation failed. The browser is "
                       "missing permissions.";
      response_status = KeyRotationCommand::Status::FAILED_INVALID_PERMISSIONS;
      break;
    case KeyRotationResult::kFailedKeyConflict:
      SYSLOG(ERROR) << "Device trust key rotation failed. Confict with the key "
                       "that exists on the server.";
      response_status = KeyRotationCommand::Status::FAILED_KEY_CONFLICT;
      break;
  }

  std::move(pending_callback_).Run(response_status);
}

void MacKeyRotationCommand::OnKeyRotationTimeout() {
  // Used to ensure that this function is being called on the main thread.
  SEQUENCE_CHECKER(sequence_checker_);

  // A callback should still be available to be run.
  if (!pending_callback_) {
    // The callback may have already run in timeout cases.
    return;
  }

  SYSLOG(ERROR) << "Device trust key rotation timed out.";
  std::move(pending_callback_).Run(KeyRotationCommand::Status::TIMED_OUT);
}

}  // namespace enterprise_connectors