chromium/chrome/browser/ash/platform_keys/key_permissions/key_permissions_service_impl.cc

// Copyright 2020 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/platform_keys/key_permissions/key_permissions_service_impl.h"

#include <stdint.h>

#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "base/base64.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/logging.h"
#include "base/values.h"
#include "chrome/browser/ash/platform_keys/key_permissions/key_permissions_manager_impl.h"
#include "chrome/browser/ash/platform_keys/platform_keys_service.h"
#include "chrome/browser/chromeos/platform_keys/platform_keys.h"
#include "chrome/common/pref_names.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_namespace.h"
#include "components/policy/core/common/policy_service.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"

namespace ash::platform_keys {

using ::chromeos::platform_keys::Status;
using ::chromeos::platform_keys::TokenId;

KeyPermissionsServiceImpl::KeyPermissionsServiceImpl(
    bool is_regular_user_profile,
    bool profile_is_managed,
    PlatformKeysService* platform_keys_service,
    KeyPermissionsManager* profile_key_permissions_manager)
    : is_regular_user_profile_(is_regular_user_profile),
      profile_is_managed_(profile_is_managed),
      platform_keys_service_(platform_keys_service),
      profile_key_permissions_manager_(profile_key_permissions_manager) {
  DCHECK(platform_keys_service_);
  DCHECK(profile_key_permissions_manager || !is_regular_user_profile);
}

KeyPermissionsServiceImpl::~KeyPermissionsServiceImpl() = default;

void KeyPermissionsServiceImpl::CanUserGrantPermissionForKey(
    std::vector<uint8_t> public_key_spki_der,
    CanUserGrantPermissionForKeyCallback callback) {
  auto key_locations_callback = base::BindOnce(
      &KeyPermissionsServiceImpl::CanUserGrantPermissionForKeyWithLocations,
      weak_factory_.GetWeakPtr(), public_key_spki_der, std::move(callback));
  platform_keys_service_->GetKeyLocations(std::move(public_key_spki_der),
                                          std::move(key_locations_callback));
}

void KeyPermissionsServiceImpl::CanUserGrantPermissionForKeyWithLocations(
    std::vector<uint8_t> public_key_spki_der,
    CanUserGrantPermissionForKeyCallback callback,
    const std::vector<TokenId>& key_locations,
    Status key_locations_retrieval_status) {
  if (key_locations_retrieval_status != Status::kSuccess) {
    LOG(ERROR) << "Key locations retrieval failed: "
               << StatusToString(key_locations_retrieval_status);
    std::move(callback).Run(/*allowed=*/false);
    return;
  }

  // It only makes sense to store the sign_unlimited flag for a key if it is on
  // a user slot. Currently, system-slot keys are implicitly corporate, so
  // CanUserGrantPermissionForKey should return false for them.
  if ((key_locations.size() != 1) || key_locations.front() != TokenId::kUser) {
    std::move(callback).Run(/*allowed=*/false);
    return;
  }

  auto bound_callback =
      base::BindOnce(&KeyPermissionsServiceImpl::
                         CanUserGrantPermissionForKeyWithLocationsAndFlag,
                     weak_factory_.GetWeakPtr(), public_key_spki_der,
                     std::move(callback), key_locations);
  IsCorporateKeyWithLocations(std::move(public_key_spki_der),
                              std::move(bound_callback), key_locations,
                              key_locations_retrieval_status);
}

void KeyPermissionsServiceImpl::
    CanUserGrantPermissionForKeyWithLocationsAndFlag(
        std::vector<uint8_t> public_key_spki_der,
        CanUserGrantPermissionForKeyCallback callback,
        const std::vector<TokenId>& key_locations,
        std::optional<bool> corporate_key,
        Status status) {
  if (status != Status::kSuccess) {
    std::move(callback).Run(/*allowed=*/false);
    return;
  }

  // As keys cannot be tagged for non-corporate usage, the user can currently
  // not grant any permissions if the profile is managed.
  if (profile_is_managed_) {
    std::move(callback).Run(/*allowed=*/false);
    return;
  }

  // If this profile is not managed but we find a corporate key, don't allow
  // the user to grant permissions.
  std::move(callback).Run(/*allowed=*/!corporate_key.value());
}

void KeyPermissionsServiceImpl::IsCorporateKey(
    std::vector<uint8_t> public_key_spki_der,
    IsCorporateKeyCallback callback) {
  auto key_locations_callback = base::BindOnce(
      &KeyPermissionsServiceImpl::IsCorporateKeyWithLocations,
      weak_factory_.GetWeakPtr(), public_key_spki_der, std::move(callback));
  platform_keys_service_->GetKeyLocations(std::move(public_key_spki_der),
                                          std::move(key_locations_callback));
}

void KeyPermissionsServiceImpl::IsCorporateKeyWithLocations(
    std::vector<uint8_t> public_key_spki_der,
    IsCorporateKeyCallback callback,
    const std::vector<TokenId>& key_locations,
    Status status) {
  if (status != Status::kSuccess) {
    LOG(ERROR) << "Key locations retrieval failed: " << StatusToString(status);
    std::move(callback).Run(/*corporate=*/std::nullopt, status);
    return;
  }

  bool key_on_user_token_only = false;
  for (const auto key_location : key_locations) {
    switch (key_location) {
      case TokenId::kUser:
        key_on_user_token_only = true;
        break;
      case TokenId::kSystem:
        KeyPermissionsManagerImpl::GetSystemTokenKeyPermissionsManager()
            ->IsKeyAllowedForUsage(
                base::BindOnce(
                    &KeyPermissionsServiceImpl::IsCorporateKeyWithKpmResponse,
                    weak_factory_.GetWeakPtr(), std::move(callback)),
                KeyUsage::kCorporate, std::move(public_key_spki_der));
        return;
    }
  }

  if (key_on_user_token_only) {
    DCHECK(is_regular_user_profile_);
    profile_key_permissions_manager_->IsKeyAllowedForUsage(
        base::BindOnce(
            &KeyPermissionsServiceImpl::IsCorporateKeyWithKpmResponse,
            weak_factory_.GetWeakPtr(), std::move(callback)),
        KeyUsage::kCorporate, std::move(public_key_spki_der));
    return;
  }

  std::move(callback).Run(/*corporate=*/false, Status::kSuccess);
}

void KeyPermissionsServiceImpl::IsCorporateKeyWithKpmResponse(
    IsCorporateKeyCallback callback,
    std::optional<bool> allowed,
    Status status) {
  if (allowed.has_value()) {
    std::move(callback).Run(allowed.value(), Status::kSuccess);
    return;
  }

  LOG(ERROR) << "Checking corporate flag via KeyPermissionsManager failed: "
             << StatusToString(status);
  std::move(callback).Run(/*corporate=*/std::nullopt, status);
}

void KeyPermissionsServiceImpl::SetCorporateKey(
    std::vector<uint8_t> public_key_spki_der,
    SetCorporateKeyCallback callback) {
  auto key_locations_callback = base::BindOnce(
      &KeyPermissionsServiceImpl::SetCorporateKeyWithLocations,
      weak_factory_.GetWeakPtr(), public_key_spki_der, std::move(callback));
  platform_keys_service_->GetKeyLocations(std::move(public_key_spki_der),
                                          std::move(key_locations_callback));
}

void KeyPermissionsServiceImpl::SetCorporateKeyWithLocations(
    std::vector<uint8_t> public_key_spki_der,
    SetCorporateKeyCallback callback,
    const std::vector<TokenId>& key_locations,
    Status key_locations_retrieval_status) {
  if (key_locations_retrieval_status != Status::kSuccess) {
    std::move(callback).Run(key_locations_retrieval_status);
    return;
  }

  if (key_locations.empty()) {
    std::move(callback).Run(Status::kErrorKeyNotFound);
    return;
  }

  // A single key location is expected because this is intended for usage after
  // key generation / import, when exactly one location is relevant.
  DCHECK_EQ(key_locations.size(), 1U);

  switch (key_locations[0]) {
    case TokenId::kSystem:
      KeyPermissionsManagerImpl::GetSystemTokenKeyPermissionsManager()
          ->AllowKeyForUsage(std::move(callback), KeyUsage::kCorporate,
                             std::move(public_key_spki_der));
      return;
    case TokenId::kUser: {
      DCHECK(is_regular_user_profile_);

      profile_key_permissions_manager_->AllowKeyForUsage(
          std::move(callback), KeyUsage::kCorporate,
          std::move(public_key_spki_der));
      return;
    }
  }
}

}  // namespace ash::platform_keys