chromium/chrome/browser/ash/platform_keys/platform_keys_service_factory.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/platform_keys_service_factory.h"

#include <utility>

#include "ash/constants/ash_features.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/scoped_observation.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/ash/kcer/kcer_factory_ash.h"
#include "chrome/browser/ash/net/client_cert_store_ash.h"
#include "chrome/browser/ash/net/client_cert_store_kcer.h"
#include "chrome/browser/ash/platform_keys/platform_keys_service.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/certificate_provider/certificate_provider.h"
#include "chrome/browser/net/nss_service.h"
#include "chrome/browser/net/nss_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/ash/components/network/system_token_cert_db_storage.h"
#include "chromeos/components/kcer/extra_instances.h"
#include "components/user_manager/user.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_context.h"
#include "net/cert/nss_cert_database.h"

namespace ash {
namespace platform_keys {

namespace {

// Retrieves the NSSCertDatabase for |context|. Must be called on the IO thread.
void GetCertDatabaseOnIoThread(
    scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner,
    PlatformKeysServiceImplDelegate::OnGotNSSCertDatabase callback,
    NssCertDatabaseGetter database_getter) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  auto [on_sync_got, on_async_got] = base::SplitOnceCallback(
      base::BindPostTask(std::move(origin_task_runner), std::move(callback)));

  net::NSSCertDatabase* cert_db =
      std::move(database_getter).Run(std::move(on_async_got));

  if (cert_db) {
    std::move(on_sync_got).Run(cert_db);
  }
}

class DelegateForUser : public PlatformKeysServiceImplDelegate {
 public:
  explicit DelegateForUser(content::BrowserContext* browser_context)
      : browser_context_(browser_context) {}
  ~DelegateForUser() override = default;

  void GetNSSCertDatabase(OnGotNSSCertDatabase callback) override {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

    content::GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(&GetCertDatabaseOnIoThread,
                       base::SingleThreadTaskRunner::GetCurrentDefault(),
                       std::move(callback),
                       NssServiceFactory::GetForContext(browser_context_)
                           ->CreateNSSCertDatabaseGetterForIOThread()));
  }

  std::unique_ptr<net::ClientCertStore> CreateClientCertStore() override {
    Profile* profile = Profile::FromBrowserContext(browser_context_);

    if (ash::features::ShouldUseKcerClientCertStore()) {
      return std::make_unique<ClientCertStoreKcer>(
          nullptr,  // no additional provider
          kcer::KcerFactoryAsh::GetKcer(profile));
    } else {
      const user_manager::User* user =
          ProfileHelper::Get()->GetUserByProfile(profile);
      // Use the device-wide system key slot only if the user is affiliated on
      // the device.
      const bool use_system_key_slot = user->IsAffiliated();
      return std::make_unique<ClientCertStoreAsh>(
          nullptr,  // no additional provider
          use_system_key_slot, user->username_hash(),
          ClientCertStoreAsh::PasswordDelegateFactory());
    }
  }

 private:
  raw_ptr<content::BrowserContext> browser_context_;
};

class DelegateForDevice : public PlatformKeysServiceImplDelegate,
                          public SystemTokenCertDbStorage::Observer {
 public:
  DelegateForDevice() {
    scoped_observation_.Observe(SystemTokenCertDbStorage::Get());
  }

  ~DelegateForDevice() override = default;

  void GetNSSCertDatabase(OnGotNSSCertDatabase callback) override {
    SystemTokenCertDbStorage::Get()->GetDatabase(std::move(callback));
  }

  std::unique_ptr<net::ClientCertStore> CreateClientCertStore() override {
    if (ash::features::ShouldUseKcerClientCertStore()) {
      return std::make_unique<ClientCertStoreKcer>(
          nullptr,  // no additional provider
          kcer::ExtraInstances::GetDeviceKcer());
    } else {
      return std::make_unique<ClientCertStoreAsh>(
          nullptr,  // no additional provider
          /*use_system_key_slot=*/true, /*username_hash=*/std::string(),
          ClientCertStoreAsh::PasswordDelegateFactory());
    }
  }

 private:
  base::ScopedObservation<SystemTokenCertDbStorage,
                          SystemTokenCertDbStorage::Observer>
      scoped_observation_{this};

  // SystemTokenCertDbStorage::Observer
  void OnSystemTokenCertDbDestroyed() override {
    scoped_observation_.Reset();
    ShutDown();
  }
};

}  // namespace

// static
PlatformKeysService* PlatformKeysServiceFactory::GetForBrowserContext(
    content::BrowserContext* context) {
  return static_cast<PlatformKeysService*>(
      GetInstance()->GetServiceForBrowserContext(context, true));
}

// static
PlatformKeysServiceFactory* PlatformKeysServiceFactory::GetInstance() {
  static base::NoDestructor<PlatformKeysServiceFactory> instance;
  return instance.get();
}

// static
PlatformKeysService* PlatformKeysServiceFactory::GetDeviceWideService() {
  if (device_wide_service_for_testing_) {
    return device_wide_service_for_testing_;
  }

  if (!device_wide_service_) {
    device_wide_service_ = std::make_unique<PlatformKeysServiceImpl>(
        std::make_unique<DelegateForDevice>());
  }

  device_wide_service_->SetMapToSoftokenAttrsForTesting(
      map_to_softoken_attrs_for_testing_);

  return device_wide_service_.get();
}

void PlatformKeysServiceFactory::SetDeviceWideServiceForTesting(
    PlatformKeysService* device_wide_service_for_testing) {
  device_wide_service_for_testing_ = device_wide_service_for_testing;
  device_wide_service_for_testing_->SetMapToSoftokenAttrsForTesting(
      map_to_softoken_attrs_for_testing_);
}

void PlatformKeysServiceFactory::SetTestingMode(bool is_testing_mode) {
  map_to_softoken_attrs_for_testing_ = is_testing_mode;
}

PlatformKeysServiceFactory::PlatformKeysServiceFactory()
    : ProfileKeyedServiceFactory(
          "PlatformKeysService",
          ProfileSelections::Builder()
              .WithRegular(ProfileSelection::kRedirectedToOriginal)
              // TODO(crbug.com/40257657): Check if this service is needed in
              // Guest mode.
              .WithGuest(ProfileSelection::kRedirectedToOriginal)
              // TODO(crbug.com/41488885): Check if this service is needed for
              // Ash Internals.
              .WithAshInternals(ProfileSelection::kRedirectedToOriginal)
              .Build()) {
  DependsOn(NssServiceFactory::GetInstance());
}

PlatformKeysServiceFactory::~PlatformKeysServiceFactory() = default;

std::unique_ptr<KeyedService>
PlatformKeysServiceFactory::BuildServiceInstanceForBrowserContext(
    content::BrowserContext* context) const {
  std::unique_ptr<PlatformKeysServiceImplDelegate> delegate;
  Profile* profile = Profile::FromBrowserContext(context);
  if (!ProfileHelper::IsUserProfile(profile)) {
    delegate = std::make_unique<DelegateForDevice>();
  } else {
    delegate = std::make_unique<DelegateForUser>(context);
  }

  std::unique_ptr<PlatformKeysServiceImpl> platform_keys_service_impl =
      std::make_unique<PlatformKeysServiceImpl>(std::move(delegate));
  platform_keys_service_impl->SetMapToSoftokenAttrsForTesting(
      map_to_softoken_attrs_for_testing_);

  return platform_keys_service_impl;
}

void PlatformKeysServiceFactory::BrowserContextShutdown(
    content::BrowserContext* context) {
  PlatformKeysService* platform_keys_service =
      static_cast<PlatformKeysService*>(
          GetServiceForBrowserContext(context, false));
  if (platform_keys_service) {
    platform_keys_service->SetMapToSoftokenAttrsForTesting(false);
  }

  BrowserContextKeyedServiceFactory::BrowserContextShutdown(context);
}

}  // namespace platform_keys
}  // namespace ash