chromium/components/ownership/owner_settings_service.cc

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

#include "components/ownership/owner_settings_service.h"

#include <cryptohi.h>
#include <keyhi.h>
#include <stdint.h>

#include <utility>

#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_runner.h"
#include "base/values.h"
#include "components/ownership/owner_key_util.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "crypto/scoped_nss_types.h"

namespace em = enterprise_management;

namespace ownership {

namespace {

using ScopedSGNContext = std::unique_ptr<
    SGNContext,
    crypto::NSSDestroyer1<SGNContext, SGN_DestroyContext, PR_TRUE>>;

crypto::ScopedSECItem SignPolicy(
    const scoped_refptr<ownership::PrivateKey>& private_key,
    base::span<const uint8_t> policy,
    const em::PolicyFetchRequest::SignatureType signature_type) {
  SECOidTag algorithm;
  switch (signature_type) {
    case em::PolicyFetchRequest::SHA1_RSA:
      algorithm = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION;
      break;
    case em::PolicyFetchRequest::SHA256_RSA:
      algorithm = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION;
      break;
    case em::PolicyFetchRequest::NONE:
      NOTREACHED();
  }

  crypto::ScopedSECItem sign_result(SECITEM_AllocItem(nullptr, nullptr, 0));

  if (SEC_SignData(sign_result.get(), policy.data(), policy.size(),
                   private_key->key(), algorithm) != SECSuccess) {
    return nullptr;
  }
  return sign_result;
}

// |public_key| is included in the |policy|
// if the ChromeSideOwnerKeyGeneration Feature is enabled. |private_key|
// actually signs the |policy| (must belong to the same key pair as
// |public_key|).
std::unique_ptr<em::PolicyFetchResponse> AssembleAndSignPolicy(
    std::unique_ptr<em::PolicyData> policy,
    scoped_refptr<ownership::PublicKey> public_key,
    scoped_refptr<ownership::PrivateKey> private_key) {
  DCHECK(private_key->key());

  // Assemble the policy.
  std::unique_ptr<em::PolicyFetchResponse> policy_response(
      new em::PolicyFetchResponse());

  policy_response->set_new_public_key(public_key->data().data(),
                                      public_key->data().size());

  if (!policy->SerializeToString(policy_response->mutable_policy_data())) {
    LOG(ERROR) << "Failed to encode policy payload.";
    return nullptr;
  }

  // Retry signature generation several times. At the low level this is
  // performed by Chaps which implements the PKCS#11 interface. It's expected
  // that some operations might fail because PKCS#11 sessions can get closed. In
  // such cases the caller should retry the request. This is not expected to
  // happen too often, 5 attempts should be enough.
  int attempt_counter = 0;
  constexpr int kMaxSignatureAttempts = 5;
  crypto::ScopedSECItem signature_item;

  em::PolicyFetchRequest::SignatureType signature_type =
      base::FeatureList::IsEnabled(ownership::kOwnerSettingsWithSha256)
          ? em::PolicyFetchRequest::SHA256_RSA
          : em::PolicyFetchRequest::SHA1_RSA;
  do {
    ++attempt_counter;
    signature_item = SignPolicy(
        private_key,
        base::as_bytes(base::make_span(policy_response->policy_data())),
        signature_type);
  } while (!signature_item && attempt_counter < kMaxSignatureAttempts);

  if (!signature_item) {
    LOG(ERROR) << "Failed to create policy signature.";
    return nullptr;
  }

  policy_response->mutable_policy_data_signature()->assign(
      reinterpret_cast<const char*>(signature_item->data), signature_item->len);
  if (base::FeatureList::IsEnabled(ownership::kOwnerSettingsWithSha256)) {
    policy_response->set_policy_data_signature_type(signature_type);
  }
  return policy_response;
}

}  // namespace

BASE_FEATURE(kOwnerSettingsWithSha256,
             "OwnerSettingsWithSha256",
             base::FEATURE_ENABLED_BY_DEFAULT);

OwnerSettingsService::OwnerSettingsService(
    const scoped_refptr<ownership::OwnerKeyUtil>& owner_key_util)
    : owner_key_util_(owner_key_util) {}

OwnerSettingsService::~OwnerSettingsService() {
  DCHECK(thread_checker_.CalledOnValidThread());
}

void OwnerSettingsService::AddObserver(Observer* observer) {
  if (observer && !observers_.HasObserver(observer))
    observers_.AddObserver(observer);
}

void OwnerSettingsService::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

bool OwnerSettingsService::IsReady() {
  DCHECK(thread_checker_.CalledOnValidThread());
  return private_key_.get();
}

bool OwnerSettingsService::IsOwner() {
  DCHECK(thread_checker_.CalledOnValidThread());
  return private_key_.get() && private_key_->key();
}

void OwnerSettingsService::IsOwnerAsync(IsOwnerCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  if (private_key_.get()) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), IsOwner()));
  } else {
    pending_is_owner_callbacks_.push_back(std::move(callback));
  }
}

bool OwnerSettingsService::AssembleAndSignPolicyAsync(
    base::TaskRunner* task_runner,
    std::unique_ptr<em::PolicyData> policy,
    AssembleAndSignPolicyAsyncCallback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());
  if (!task_runner || !IsOwner())
    return false;
  // |public_key_| is explicitly forwarded down to
  // |OwnerSettingsServiceAsh::OnSignedPolicyStored()| to make sure that only
  // the key that was actually included in a policy gets marked as persisted
  // (theoretically a different key can be re-assigned to |public_key_| in
  // between the async calls).
  return task_runner->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&AssembleAndSignPolicy, std::move(policy), public_key_,
                     private_key_),
      base::BindOnce(std::move(callback), public_key_));
}

bool OwnerSettingsService::SetBoolean(const std::string& setting, bool value) {
  DCHECK(thread_checker_.CalledOnValidThread());
  base::Value in_value(value);
  return Set(setting, in_value);
}

bool OwnerSettingsService::SetInteger(const std::string& setting, int value) {
  DCHECK(thread_checker_.CalledOnValidThread());
  base::Value in_value(value);
  return Set(setting, in_value);
}

bool OwnerSettingsService::SetDouble(const std::string& setting, double value) {
  DCHECK(thread_checker_.CalledOnValidThread());
  base::Value in_value(value);
  return Set(setting, in_value);
}

void OwnerSettingsService::RunPendingIsOwnerCallbacksForTesting(bool is_owner) {
  std::vector<IsOwnerCallback> is_owner_callbacks;
  is_owner_callbacks.swap(pending_is_owner_callbacks_);
  for (auto& callback : is_owner_callbacks)
    std::move(callback).Run(is_owner);
}

bool OwnerSettingsService::SetString(const std::string& setting,
                                     const std::string& value) {
  DCHECK(thread_checker_.CalledOnValidThread());
  base::Value in_value(value);
  return Set(setting, in_value);
}

void OwnerSettingsService::ReloadKeypair() {
  ReloadKeypairImpl(
      base::BindOnce(&OwnerSettingsService::OnKeypairLoaded, as_weak_ptr()));
}

void OwnerSettingsService::OnKeypairLoaded(
    scoped_refptr<PublicKey> public_key,
    scoped_refptr<PrivateKey> private_key) {
  DCHECK(thread_checker_.CalledOnValidThread());

  // The pointers themself should not be null to indicate that the keys finished
  // loading (even if unsuccessfully). Absence of the actual data inside can
  // indicate that the keys are unavailable.
  public_key_ =
      public_key ? public_key
                 : base::MakeRefCounted<ownership::PublicKey>(
                       /*is_persisted=*/false, /*data=*/std::vector<uint8_t>());
  private_key_ = private_key
                     ? private_key
                     : base::MakeRefCounted<ownership::PrivateKey>(nullptr);

  std::vector<IsOwnerCallback> is_owner_callbacks;
  is_owner_callbacks.swap(pending_is_owner_callbacks_);

  const bool is_owner = IsOwner();
  for (auto& callback : is_owner_callbacks)
    std::move(callback).Run(is_owner);

  OnPostKeypairLoadedActions();
}

}  // namespace ownership