chromium/chrome/browser/certificate_manager_model_ash_browsertest.cc

// Copyright 2024 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/certificate_manager_model.h"

#include <string>

#include "base/files/file_util.h"
#include "base/test/test_future.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/ash/crosapi/cert_database_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/login/test/logged_in_user_mixin.h"
#include "chrome/browser/net/nss_service.h"
#include "chrome/browser/net/nss_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/browser_test.h"
#include "crypto/scoped_test_nss_db.h"
#include "net/test/test_data_directory.h"

// The correct password for the client.p12 file.
const char16_t kPassword[] = u"12345";

struct FakeObserver : CertificateManagerModel::Observer {
  void CertificatesRefreshed() override {}
};

class CertificateManagerModelBrowserTestBase
    : public MixinBasedInProcessBrowserTest {
 public:
  void SetUpOnMainThread() override {
    MixinBasedInProcessBrowserTest::SetUpOnMainThread();
    logged_in_user_mixin_.LogInUser();

    base::test::TestFuture<std::unique_ptr<CertificateManagerModel>>
        model_waiter;
    CertificateManagerModel::Create(browser()->profile(), &fake_observer_,
                                    model_waiter.GetCallback());
    certificate_manager_model_ = model_waiter.Take();
  }

  void TearDownOnMainThread() override { certificate_manager_model_.reset(); }

  std::string ReadTestFile(const std::string& file_name) {
    base::ScopedAllowBlockingForTesting allow_blocking;
    base::FilePath file_path =
        net::GetTestCertsDirectory().AppendASCII(file_name);
    std::string file_data;
    if (!base::ReadFileToString(file_path, &file_data)) {
      ADD_FAILURE() << "Couldn't read " << file_path;
      return {};
    }
    return file_data;
  }

  const std::string& GetPkcs12() {
    static const base::NoDestructor<std::string> pkcs12(
        ReadTestFile("client.p12"));
    return *pkcs12;
  }

 protected:
  ash::LoggedInUserMixin logged_in_user_mixin_{
      &mixin_host_, /*test_base=*/this, embedded_test_server(),
      ash::LoggedInUserMixin::LogInType::kConsumer};

  FakeObserver fake_observer_;
  base::test::ScopedFeatureList feature_list_;
  std::unique_ptr<crypto::ScopedTestNSSDB> public_slot_ =
      std::make_unique<crypto::ScopedTestNSSDB>();
  std::unique_ptr<CertificateManagerModel> certificate_manager_model_;
};

// Test that when OnPkcs12CertDualWritten() is called from Lacros, the
// kNssChapsDualWrittenCertsExist preference is stored.
IN_PROC_BROWSER_TEST_F(CertificateManagerModelBrowserTestBase,
                       LacrosCallStoresThePref) {
  EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
      prefs::kNssChapsDualWrittenCertsExist));

  crosapi::CertDatabaseAsh* const cert_database_ash =
      crosapi::CrosapiManager::Get()->crosapi_ash()->cert_database_ash();
  cert_database_ash->OnPkcs12CertDualWritten();

  EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
      prefs::kNssChapsDualWrittenCertsExist));
}

class CertificateManagerModelEnablePkcs12DualWrite
    : public CertificateManagerModelBrowserTestBase {
 public:
  CertificateManagerModelEnablePkcs12DualWrite() {
    feature_list_.InitAndEnableFeature(
        chromeos::features::kEnablePkcs12ToChapsDualWrite);
  }
};

// Test ImportFromPKCS12 with dual-write enabled.
IN_PROC_BROWSER_TEST_F(CertificateManagerModelEnablePkcs12DualWrite,
                       DualWriteIsEnabled) {
  EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
      prefs::kNssChapsDualWrittenCertsExist));

  // Non-extractable certs should not be dual-written and should not set the
  // related preference.
  {
    base::test::TestFuture<int> import_waiter;
    certificate_manager_model_->ImportFromPKCS12(
        public_slot_->slot(), GetPkcs12(), kPassword,
        /*is_extractable=*/false, import_waiter.GetCallback());
    EXPECT_EQ(import_waiter.Get(), net::OK);
    EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
        prefs::kNssChapsDualWrittenCertsExist));
  }

  // Extractable certs should be dual-written and should set the related
  // preference.
  {
    base::test::TestFuture<int> import_waiter;
    certificate_manager_model_->ImportFromPKCS12(
        public_slot_->slot(), GetPkcs12(), kPassword,
        /*is_extractable=*/true, import_waiter.GetCallback());
    EXPECT_EQ(import_waiter.Get(), net::OK);
    EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
        prefs::kNssChapsDualWrittenCertsExist));
  }
}

class CertificateManagerModelDisablePkcs12DualWrite
    : public CertificateManagerModelBrowserTestBase {
 public:
  CertificateManagerModelDisablePkcs12DualWrite() {
    feature_list_.InitAndDisableFeature(
        chromeos::features::kEnablePkcs12ToChapsDualWrite);
  }
};

// Test ImportFromPKCS12 with dual-write disabled. Everything should work as
// usual, kNssChapsDualWrittenCertsExist preference should not be set for all
// cases.
IN_PROC_BROWSER_TEST_F(CertificateManagerModelDisablePkcs12DualWrite,
                       DualWriteIsDisabled) {
  EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
      prefs::kNssChapsDualWrittenCertsExist));

  {
    base::test::TestFuture<int> import_waiter;
    certificate_manager_model_->ImportFromPKCS12(
        public_slot_->slot(), GetPkcs12(), kPassword,
        /*is_extractable=*/false, import_waiter.GetCallback());
    EXPECT_EQ(import_waiter.Get(), net::OK);
    EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
        prefs::kNssChapsDualWrittenCertsExist));
  }

  {
    base::test::TestFuture<int> import_waiter;
    certificate_manager_model_->ImportFromPKCS12(
        public_slot_->slot(), GetPkcs12(), kPassword,
        /*is_extractable=*/true, import_waiter.GetCallback());
    EXPECT_EQ(import_waiter.Get(), net::OK);
    EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
        prefs::kNssChapsDualWrittenCertsExist));
  }
}