| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| #include <string> |
| |
| #include "ash/constants/ash_switches.h" |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/json/json_writer.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/task/current_thread.h" |
| #include "base/test/test_future.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/ash/login/existing_user_controller.h" |
| #include "chrome/browser/ash/login/helper.h" |
| #include "chrome/browser/ash/login/startup_utils.h" |
| #include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h" |
| #include "chrome/browser/ash/login/test/login_or_lock_screen_visible_waiter.h" |
| #include "chrome/browser/ash/login/test/oobe_base_test.h" |
| #include "chrome/browser/ash/login/test/oobe_screen_waiter.h" |
| #include "chrome/browser/ash/login/test/session_manager_state_waiter.h" |
| #include "chrome/browser/ash/login/ui/login_display_host.h" |
| #include "chrome/browser/ash/login/wizard_controller.h" |
| #include "chrome/browser/ash/policy/core/device_local_account.h" |
| #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h" |
| #include "chrome/browser/ash/policy/login/login_policy_test_base.h" |
| #include "chrome/browser/ash/policy/login/signin_profile_extensions_policy_test_base.h" |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/net/nss_service.h" |
| #include "chrome/browser/net/nss_service_factory.h" |
| #include "chrome/browser/policy/networking/user_network_configuration_updater_ash.h" |
| #include "chrome/browser/policy/networking/user_network_configuration_updater_factory.h" |
| #include "chrome/browser/policy/profile_policy_connector_builder.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/webui/ash/login/gaia_screen_handler.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chromeos/ash/components/network/network_cert_loader.h" |
| #include "chromeos/ash/components/network/onc/onc_certificate_importer.h" |
| #include "chromeos/ash/components/network/onc/onc_certificate_importer_impl.h" |
| #include "chromeos/ash/components/network/policy_certificate_provider.h" |
| #include "chromeos/components/onc/onc_test_utils.h" |
| #include "chromeos/test/chromeos_test_utils.h" |
| #include "components/onc/onc_constants.h" |
| #include "components/policy/core/browser/browser_policy_connector.h" |
| #include "components/policy/core/common/cloud/cloud_policy_constants.h" |
| #include "components/policy/core/common/mock_configuration_policy_provider.h" |
| #include "components/policy/core/common/policy_switches.h" |
| #include "components/policy/policy_constants.h" |
| #include "components/policy/proto/cloud_policy.pb.h" |
| #include "components/session_manager/core/session_manager.h" |
| #include "components/user_manager/user.h" |
| #include "components/user_manager/user_manager.h" |
| #include "components/user_manager/user_type.h" |
| #include "components/version_info/version_info.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/test_utils.h" |
| #include "crypto/scoped_test_nss_db.h" |
| #include "extensions/browser/extension_host_test_helper.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_util.h" |
| #include "extensions/browser/test_extension_registry_observer.h" |
| #include "net/cert/cert_database.h" |
| #include "net/cert/nss_cert_database.h" |
| #include "net/cert/test_root_certs.h" |
| #include "net/cert/x509_util_nss.h" |
| #include "net/test/cert_test_util.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/mojom/network_context.mojom.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace policy { |
| |
| namespace { |
| |
| namespace em = ::enterprise_management; |
| |
| // Test data file storing an ONC blob with an Authority certificate. |
| constexpr char kRootCaCertOnc[] = "root-ca-cert.onc"; |
| constexpr char kClientCertOnc[] = "certificate-client.onc"; |
| constexpr char kRootAndIntermediateCaCertsOnc[] = |
| "root-and-intermediate-ca-certs.onc"; |
| constexpr char kClientCertSubjectCommonName[] = "lmao"; |
| // A PEM-encoded certificate which was signed by the Authority specified in |
| // |kRootCaCertOnc|. |
| constexpr char kServerCert[] = "ok_cert.pem"; |
| // A PEM-encoded certificate which was signed by the intermediate Authority |
| // specified in |kRootAndIntermediateCaCertsOnc|. |
| constexpr char kServerCertByIntermediate[] = "ok_cert_by_intermediate.pem"; |
| // The PEM-encoded Authority certificate specified by |kRootCaCertOnc|. |
| constexpr char kRootCaCert[] = "root_ca_cert.pem"; |
| constexpr char kDeviceLocalAccountId[] = "dla1@example.com"; |
| |
| constexpr char kSigninScreenExtension1[] = "ngjobkbdodapjbbncmagbccommkggmnj"; |
| constexpr char kSigninScreenExtension1UpdateManifestPath[] = |
| "/extensions/signin_screen_manual_test_extension/update_manifest.xml"; |
| |
| const char kSigninScreenExtension2[] = "oclffehlkdgibkainkilopaalpdobkan"; |
| const char kSigninScreenExtension2UpdateManifestPath[] = |
| "/extensions/api_test/login_screen_apis/update_manifest.xml"; |
| |
| // Allows waiting until the list of policy-pushed web-trusted certificates |
| // changes. |
| class WebTrustedCertsChangedObserver |
| : public ash::PolicyCertificateProvider::Observer { |
| public: |
| WebTrustedCertsChangedObserver() = default; |
| |
| WebTrustedCertsChangedObserver(const WebTrustedCertsChangedObserver&) = |
| delete; |
| WebTrustedCertsChangedObserver& operator=( |
| const WebTrustedCertsChangedObserver&) = delete; |
| |
| // ash::PolicyCertificateProvider::Observer |
| void OnPolicyProvidedCertsChanged() override { run_loop_.Quit(); } |
| |
| void Wait() { run_loop_.Run(); } |
| |
| private: |
| base::RunLoop run_loop_; |
| }; |
| |
| // Allows waiting until the |CertDatabase| notifies its observers that it has |
| // changd. |
| class CertDatabaseChangedObserver : public net::CertDatabase::Observer { |
| public: |
| CertDatabaseChangedObserver() {} |
| |
| CertDatabaseChangedObserver(const CertDatabaseChangedObserver&) = delete; |
| CertDatabaseChangedObserver& operator=(const CertDatabaseChangedObserver&) = |
| delete; |
| |
| void OnCertDBChanged() override { run_loop_.Quit(); } |
| |
| void Wait() { run_loop_.Run(); } |
| |
| private: |
| base::RunLoop run_loop_; |
| }; |
| |
| // Retrieves the path to the directory containing certificates designated for |
| // testing of policy-provided certificates into *|out_test_certs_path|. |
| base::FilePath GetTestCertsPath() { |
| base::FilePath test_data_dir; |
| EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir)); |
| |
| base::FilePath test_certs_path = |
| test_data_dir.AppendASCII("policy").AppendASCII("test_certs"); |
| base::ScopedAllowBlockingForTesting allow_io; |
| EXPECT_TRUE(base::DirectoryExists(test_certs_path)); |
| return test_certs_path; |
| } |
| |
| // Reads the contents of the file with name |file_name| in the directory |
| // returned by GetTestCertsPath into *|out_file_contents|. |
| std::string GetTestCertsFileContents(const std::string& file_name) { |
| base::FilePath test_certs_path = GetTestCertsPath(); |
| |
| base::ScopedAllowBlockingForTesting allow_io; |
| std::string file_contents; |
| EXPECT_TRUE(base::ReadFileToString(test_certs_path.AppendASCII(file_name), |
| &file_contents)); |
| return file_contents; |
| } |
| |
| // Allows setting user policy to assign trust to the CA certificate specified by |
| // |kRootCaCert|. |
| class UserPolicyCertsHelper { |
| public: |
| UserPolicyCertsHelper() = default; |
| ~UserPolicyCertsHelper() = default; |
| |
| UserPolicyCertsHelper(const UserPolicyCertsHelper& other) = delete; |
| UserPolicyCertsHelper& operator=(const UserPolicyCertsHelper& other) = delete; |
| |
| // Reads in testing certificates. |
| // Run in ASSERT_NO_FATAL_FAILURE. |
| void Initialize() { |
| base::ScopedAllowBlockingForTesting allow_io; |
| base::FilePath test_certs_path = GetTestCertsPath(); |
| |
| base::FilePath server_cert_path = test_certs_path.AppendASCII(kServerCert); |
| server_cert_ = net::ImportCertFromFile(server_cert_path.DirName(), |
| server_cert_path.BaseName().value()); |
| ASSERT_TRUE(server_cert_); |
| |
| base::FilePath root_cert_path = test_certs_path.AppendASCII(kRootCaCert); |
| root_cert_ = net::ImportCertFromFile(root_cert_path.DirName(), |
| root_cert_path.BaseName().value()); |
| ASSERT_TRUE(root_cert_); |
| |
| base::FilePath server_cert_by_intermediate_path = |
| test_certs_path.AppendASCII(kServerCertByIntermediate); |
| server_cert_by_intermediate_ = net::ImportCertFromFile( |
| server_cert_path.DirName(), |
| server_cert_by_intermediate_path.BaseName().value()); |
| ASSERT_TRUE(server_cert_by_intermediate_); |
| } |
| |
| // Sets the ONC-policy to the blob defined by |kRootCaCertOnc| and waits until |
| // the notification that policy-provided trust roots have changed is sent from |
| // |profile|'s UserNetworkConfigurationUpdater. |
| void SetRootCertONCUserPolicy( |
| Profile* profile, |
| MockConfigurationPolicyProvider* mock_policy_provider) { |
| std::string onc_policy_data = GetTestCertsFileContents(kRootCaCertOnc); |
| SetONCUserPolicy(profile, mock_policy_provider, onc_policy_data); |
| } |
| |
| // Sets the ONC-policy to the blob defined by |kRootCaCertOnc| and waits until |
| // the notification that policy-provided trust roots have changed is sent from |
| // |profile|'s UserNetworkConfigurationUpdater. |
| void SetRootAndIntermediateCertsONCUserPolicy( |
| Profile* profile, |
| MockConfigurationPolicyProvider* mock_policy_provider) { |
| std::string onc_policy_data = |
| GetTestCertsFileContents(kRootAndIntermediateCaCertsOnc); |
| SetONCUserPolicy(profile, mock_policy_provider, onc_policy_data); |
| } |
| |
| const scoped_refptr<net::X509Certificate>& server_cert() { |
| return server_cert_; |
| } |
| |
| const scoped_refptr<net::X509Certificate>& root_cert() { return root_cert_; } |
| |
| const scoped_refptr<net::X509Certificate>& server_cert_by_intermediate() { |
| return server_cert_by_intermediate_; |
| } |
| |
| private: |
| void SetONCUserPolicy(Profile* profile, |
| MockConfigurationPolicyProvider* mock_policy_provider, |
| const std::string& onc_policy_data) { |
| NetworkConfigurationUpdater* user_network_configuration_updater = |
| UserNetworkConfigurationUpdaterFactory::GetForBrowserContext(profile); |
| |
| WebTrustedCertsChangedObserver trust_roots_changed_observer; |
| user_network_configuration_updater->AddPolicyProvidedCertsObserver( |
| &trust_roots_changed_observer); |
| |
| PolicyMap policy; |
| policy.Set(key::kOpenNetworkConfiguration, POLICY_LEVEL_MANDATORY, |
| POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, |
| base::Value(onc_policy_data), nullptr); |
| mock_policy_provider->UpdateChromePolicy(policy); |
| // Note that this relies on the implementation detail that the notification |
| // is sent even if the trust roots effectively remain the same. |
| trust_roots_changed_observer.Wait(); |
| user_network_configuration_updater->RemovePolicyProvidedCertsObserver( |
| &trust_roots_changed_observer); |
| } |
| |
| // Server Certificate which is signed by authority specified in |kRootCaCert|. |
| scoped_refptr<net::X509Certificate> server_cert_; |
| // Authority Certificate specified in |kRootCaCert|. |
| scoped_refptr<net::X509Certificate> root_cert_; |
| // Server Certificate which is signed by an intermediate authority, which |
| // itself is signed by the authority specified in |kRootCaCert|. |
| // |kRootCaCertOnc| does not know this intermediate authority. |
| // |kRootCaAndIntermediateCertsOnc| does know this intermediate authority, but |
| // does not request web trust for it. Instead, trust should be delegate from |
| // the root authrotiy. |
| scoped_refptr<net::X509Certificate> server_cert_by_intermediate_; |
| }; |
| |
| // Verifies |certificate| with |storage_partition|'s CertVerifier and returns |
| // the result. |
| int VerifyTestServerCertInStoragePartition( |
| content::StoragePartition* storage_partition, |
| const scoped_refptr<net::X509Certificate>& certificate) { |
| base::test::TestFuture<int> future; |
| storage_partition->GetNetworkContext()->VerifyCertificateForTesting( |
| certificate, "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), future.GetCallback()); |
| return future.Get(); |
| } |
| |
| // Verifies |certificate| with the CertVerifier for |profile|'s default |
| // StoragePartition and returns the result. |
| int VerifyTestServerCert( |
| Profile* profile, |
| const scoped_refptr<net::X509Certificate>& certificate) { |
| return VerifyTestServerCertInStoragePartition( |
| profile->GetDefaultStoragePartition(), certificate); |
| } |
| |
| // Returns true if |cert_handle| refers to a certificate that has a subject |
| // CommonName equal to |subject_common_name|. |
| bool HasSubjectCommonName(CERTCertificate* cert_handle, |
| const std::string& subject_common_name) { |
| char* nss_text = CERT_GetCommonName(&cert_handle->subject); |
| if (!nss_text) |
| return false; |
| |
| const bool result = subject_common_name == nss_text; |
| PORT_Free(nss_text); |
| |
| return result; |
| } |
| |
| void IsCertInNSSDatabaseOnIOThreadWithCertList( |
| const std::string& subject_common_name, |
| bool* out_system_slot_available, |
| base::OnceClosure done_closure, |
| net::ScopedCERTCertificateList certs) { |
| for (const net::ScopedCERTCertificate& cert : certs) { |
| if (HasSubjectCommonName(cert.get(), subject_common_name)) { |
| *out_system_slot_available = true; |
| break; |
| } |
| } |
| std::move(done_closure).Run(); |
| } |
| |
| void IsCertInNSSDatabaseOnIOThreadWithCertDb( |
| const std::string& subject_common_name, |
| bool* out_system_slot_available, |
| base::OnceClosure done_closure, |
| net::NSSCertDatabase* cert_db) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| cert_db->ListCerts(base::BindOnce( |
| &IsCertInNSSDatabaseOnIOThreadWithCertList, subject_common_name, |
| out_system_slot_available, std::move(done_closure))); |
| } |
| |
| void IsCertInNSSDatabaseOnIOThread(NssCertDatabaseGetter database_getter, |
| const std::string& subject_common_name, |
| bool* out_cert_found, |
| base::OnceClosure done_closure) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| auto did_get_cert_db_split_callback = base::SplitOnceCallback(base::BindOnce( |
| &IsCertInNSSDatabaseOnIOThreadWithCertDb, subject_common_name, |
| out_cert_found, std::move(done_closure))); |
| |
| net::NSSCertDatabase* cert_db = |
| std::move(database_getter) |
| .Run(std::move(did_get_cert_db_split_callback.first)); |
| if (cert_db) { |
| std::move(did_get_cert_db_split_callback.second).Run(cert_db); |
| } |
| } |
| |
| // Returns true if a certificate with subject CommonName |common_name| is |
| // present in the |NSSCertDatbase| for |profile|. |
| bool IsCertInNSSDatabase(Profile* profile, |
| const std::string& subject_common_name) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| base::RunLoop run_loop; |
| bool cert_found = false; |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(IsCertInNSSDatabaseOnIOThread, |
| NssServiceFactory::GetForContext(profile) |
| ->CreateNSSCertDatabaseGetterForIOThread(), |
| subject_common_name, &cert_found, run_loop.QuitClosure())); |
| run_loop.Run(); |
| return cert_found; |
| } |
| |
| } // namespace |
| |
| // Base class for testing policy-provided trust roots with device-local |
| // accounts. Needs device policy. |
| class PolicyProvidedCertsDeviceLocalAccountTest |
| : public DevicePolicyCrosBrowserTest { |
| public: |
| PolicyProvidedCertsDeviceLocalAccountTest() { |
| // Use the same testing slot as private and public slot for testing. |
| test_nss_cert_db_ = std::make_unique<net::NSSCertDatabase>( |
| crypto::ScopedPK11Slot( |
| PK11_ReferenceSlot(test_nssdb_.slot())) /* public slot */, |
| crypto::ScopedPK11Slot( |
| PK11_ReferenceSlot(test_nssdb_.slot())) /* private slot */); |
| } |
| |
| protected: |
| virtual void SetupDevicePolicy() = 0; |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| DevicePolicyCrosBrowserTest::SetUpInProcessBrowserTestFixture(); |
| |
| ASSERT_NO_FATAL_FAILURE(user_policy_certs_helper_.Initialize()); |
| |
| // Set up the mock policy provider. |
| EXPECT_CALL(user_policy_provider_, IsInitializationComplete(testing::_)) |
| .WillRepeatedly(testing::Return(true)); |
| EXPECT_CALL(user_policy_provider_, IsFirstPolicyLoadComplete(testing::_)) |
| .WillRepeatedly(testing::Return(true)); |
| BrowserPolicyConnector::SetPolicyProviderForTesting(&user_policy_provider_); |
| |
| device_policy()->policy_data().set_public_key_version(1); |
| em::ChromeDeviceSettingsProto& proto(device_policy()->payload()); |
| proto.mutable_show_user_names()->set_show_user_names(true); |
| |
| SetupDevicePolicy(); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| DevicePolicyCrosBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(ash::switches::kLoginManager); |
| command_line->AppendSwitch(ash::switches::kForceLoginManagerInTests); |
| command_line->AppendSwitchASCII(ash::switches::kLoginProfile, "user"); |
| command_line->AppendSwitch(ash::switches::kOobeSkipPostLogin); |
| } |
| |
| ash::EmbeddedPolicyTestServerMixin policy_test_server_mixin_{&mixin_host_}; |
| |
| const AccountId device_local_account_id_ = |
| AccountId::FromUserEmail(GenerateDeviceLocalAccountUserId( |
| kDeviceLocalAccountId, |
| DeviceLocalAccount::TYPE_PUBLIC_SESSION)); |
| |
| MockConfigurationPolicyProvider user_policy_provider_; |
| UserPolicyCertsHelper user_policy_certs_helper_; |
| |
| crypto::ScopedTestNSSDB test_nssdb_; |
| std::unique_ptr<net::NSSCertDatabase> test_nss_cert_db_; |
| }; |
| |
| // Sets up device policy for public session and provides functions to sing into |
| // it. |
| class PolicyProvidedCertsPublicSessionTest |
| : public PolicyProvidedCertsDeviceLocalAccountTest { |
| protected: |
| // PolicyProvidedCertsDeviceLocalAccountTest: |
| void SetupDevicePolicy() override { |
| em::ChromeDeviceSettingsProto& proto(device_policy()->payload()); |
| em::DeviceLocalAccountInfoProto* account = |
| proto.mutable_device_local_accounts()->add_account(); |
| account->set_account_id(kDeviceLocalAccountId); |
| account->set_type( |
| em::DeviceLocalAccountInfoProto::ACCOUNT_TYPE_PUBLIC_SESSION); |
| RefreshDevicePolicy(); |
| policy_test_server_mixin_.UpdateDevicePolicy(proto); |
| } |
| |
| // TODO(crbug/874831): Consider migrating to LoggedInMixin and deprecating |
| // this function. |
| void StartLogin() { |
| auto* const wizard_controller = ash::WizardController::default_controller(); |
| ASSERT_TRUE(wizard_controller); |
| wizard_controller->SkipToLoginForTesting(); |
| |
| ash::LoginOrLockScreenVisibleWaiter().Wait(); |
| |
| // Login into the public session. |
| auto* controller = ash::ExistingUserController::current_controller(); |
| ASSERT_TRUE(controller); |
| ash::UserContext user_context(user_manager::USER_TYPE_PUBLIC_ACCOUNT, |
| device_local_account_id_); |
| controller->Login(user_context, ash::SigninSpecifics()); |
| } |
| }; |
| |
| // TODO(https://crbug.com/874831): Re-enable this after the source of the |
| // flakiness has been identified. |
| IN_PROC_BROWSER_TEST_F(PolicyProvidedCertsPublicSessionTest, |
| DISABLED_AllowedInPublicSession) { |
| StartLogin(); |
| ash::test::WaitForPrimaryUserSessionStart(); |
| |
| BrowserList* browser_list = BrowserList::GetInstance(); |
| EXPECT_EQ(1U, browser_list->size()); |
| Browser* browser = browser_list->get(0); |
| ASSERT_TRUE(browser); |
| |
| user_policy_certs_helper_.SetRootCertONCUserPolicy(browser->profile(), |
| &user_policy_provider_); |
| EXPECT_EQ(net::OK, |
| VerifyTestServerCert(browser->profile(), |
| user_policy_certs_helper_.server_cert())); |
| } |
| |
| class PolicyProvidedCertsOnUserSessionInitTest : public LoginPolicyTestBase { |
| public: |
| PolicyProvidedCertsOnUserSessionInitTest( |
| const PolicyProvidedCertsOnUserSessionInitTest&) = delete; |
| PolicyProvidedCertsOnUserSessionInitTest& operator=( |
| const PolicyProvidedCertsOnUserSessionInitTest&) = delete; |
| |
| protected: |
| PolicyProvidedCertsOnUserSessionInitTest() {} |
| |
| void GetPolicySettings(em::CloudPolicySettings* policy) const override { |
| std::string user_policy_blob = GetTestCertsFileContents(kRootCaCertOnc); |
| policy->mutable_opennetworkconfiguration()->set_value(user_policy_blob); |
| } |
| |
| Profile* active_user_profile() { |
| const user_manager::User* const user = |
| user_manager::UserManager::Get()->GetActiveUser(); |
| Profile* const profile = ash::ProfileHelper::Get()->GetProfileByUser(user); |
| return profile; |
| } |
| }; |
| |
| // Verifies that the policy-provided trust root is active as soon as the user |
| // session starts. |
| IN_PROC_BROWSER_TEST_F(PolicyProvidedCertsOnUserSessionInitTest, |
| TrustAnchorsAvailableImmediatelyAfterSessionStart) { |
| // Load the certificate which is only OK if the policy-provided authority is |
| // actually trusted. |
| base::FilePath test_certs_path = GetTestCertsPath(); |
| |
| base::FilePath server_cert_path = test_certs_path.AppendASCII(kServerCert); |
| scoped_refptr<net::X509Certificate> server_cert = net::ImportCertFromFile( |
| server_cert_path.DirName(), server_cert_path.BaseName().value()); |
| |
| OobeBaseTest::WaitForSigninScreen(); |
| TriggerLogIn(); |
| |
| ash::test::WaitForPrimaryUserSessionStart(); |
| EXPECT_EQ(net::OK, VerifyTestServerCert(active_user_profile(), server_cert)); |
| } |
| |
| // Testing policy-provided client cert import. |
| class PolicyProvidedClientCertsTest : public DevicePolicyCrosBrowserTest { |
| protected: |
| PolicyProvidedClientCertsTest() {} |
| ~PolicyProvidedClientCertsTest() override {} |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| // Set up the mock policy provider. |
| EXPECT_CALL(provider_, IsInitializationComplete(testing::_)) |
| .WillRepeatedly(testing::Return(true)); |
| EXPECT_CALL(provider_, IsFirstPolicyLoadComplete(testing::_)) |
| .WillRepeatedly(testing::Return(true)); |
| BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_); |
| |
| DevicePolicyCrosBrowserTest::SetUpInProcessBrowserTestFixture(); |
| } |
| |
| // Sets the ONC-policy to the blob defined by |kClientCertOnc|. Then waits |
| // until |CertDatabase| notifies its observers that the database has changed. |
| void SetClientCertONCPolicy(Profile* profile) { |
| net::CertDatabase* cert_database = net::CertDatabase::GetInstance(); |
| CertDatabaseChangedObserver cert_database_changed_observer; |
| cert_database->AddObserver(&cert_database_changed_observer); |
| |
| const std::string& user_policy_blob = |
| chromeos::onc::test_utils::ReadTestData(kClientCertOnc); |
| PolicyMap policy; |
| policy.Set(key::kOpenNetworkConfiguration, POLICY_LEVEL_MANDATORY, |
| POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, |
| base::Value(user_policy_blob), nullptr); |
| provider_.UpdateChromePolicy(policy); |
| |
| cert_database_changed_observer.Wait(); |
| cert_database->RemoveObserver(&cert_database_changed_observer); |
| } |
| |
| private: |
| MockConfigurationPolicyProvider provider_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PolicyProvidedClientCertsTest, ClientCertsImported) { |
| // Sanity check: we don't expect the client certificate to be present before |
| // setting the user ONC policy. |
| EXPECT_FALSE( |
| IsCertInNSSDatabase(browser()->profile(), kClientCertSubjectCommonName)); |
| |
| SetClientCertONCPolicy(browser()->profile()); |
| EXPECT_TRUE( |
| IsCertInNSSDatabase(browser()->profile(), kClientCertSubjectCommonName)); |
| } |
| |
| // TODO(https://crbug.com/874937): Add a test case for a kiosk session. |
| |
| // Class for testing policy-provided extensions in the sign-in profile. |
| // Sets a device policy which applies the |kRootCaCert| for |
| // |kSigninScreenExtension1|. Force-installs |kSigninScreenExtension1| and |
| // |kSigninScreenExtension2| into the sign-in profile. |
| class PolicyProvidedCertsForSigninExtensionTest |
| : public SigninProfileExtensionsPolicyTestBase { |
| public: |
| PolicyProvidedCertsForSigninExtensionTest( |
| const PolicyProvidedCertsForSigninExtensionTest&) = delete; |
| PolicyProvidedCertsForSigninExtensionTest& operator=( |
| const PolicyProvidedCertsForSigninExtensionTest&) = delete; |
| |
| protected: |
| // Use DEV channel as sign-in screen extensions are currently usable there. |
| PolicyProvidedCertsForSigninExtensionTest() |
| : SigninProfileExtensionsPolicyTestBase(version_info::Channel::DEV) {} |
| ~PolicyProvidedCertsForSigninExtensionTest() override = default; |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| // Apply |kRootCaCert| for |kSigninScreenExtension1| in Device ONC policy. |
| base::FilePath test_certs_path = GetTestCertsPath(); |
| std::string x509_contents; |
| { |
| base::ScopedAllowBlockingForTesting allow_io; |
| ASSERT_TRUE(base::ReadFileToString( |
| test_certs_path.AppendASCII(kRootCaCert), &x509_contents)); |
| } |
| |
| base::Value::Dict onc_dict = BuildONCForExtensionScopedCertificate( |
| x509_contents, kSigninScreenExtension1); |
| ASSERT_TRUE(base::JSONWriter::Write( |
| onc_dict, device_policy() |
| ->payload() |
| .mutable_open_network_configuration() |
| ->mutable_open_network_configuration())); |
| |
| // Load the certificate which is only OK if the policy-provided authority is |
| // actually trusted. |
| base::FilePath server_cert_path = test_certs_path.AppendASCII(kServerCert); |
| server_cert_ = net::ImportCertFromFile(server_cert_path.DirName(), |
| server_cert_path.BaseName().value()); |
| ASSERT_TRUE(server_cert_); |
| |
| SigninProfileExtensionsPolicyTestBase::SetUpInProcessBrowserTestFixture(); |
| } |
| |
| void SetUpOnMainThread() override { |
| ash::StartupUtils::MarkOobeCompleted(); // Pretend that OOBE was complete. |
| |
| SigninProfileExtensionsPolicyTestBase::SetUpOnMainThread(); |
| |
| signin_profile_ = GetInitialProfile(); |
| ASSERT_TRUE(ash::ProfileHelper::IsSigninProfile(signin_profile_)); |
| |
| extensions::ExtensionHostTestHelper extension_1_observer( |
| signin_profile_, kSigninScreenExtension1); |
| extension_1_observer.RestrictToType( |
| extensions::mojom::ViewType::kExtensionBackgroundPage); |
| extensions::ExtensionHostTestHelper extension_2_observer( |
| signin_profile_, kSigninScreenExtension2); |
| extension_2_observer.RestrictToType( |
| extensions::mojom::ViewType::kExtensionBackgroundPage); |
| |
| AddExtensionForForceInstallation(kSigninScreenExtension1, |
| kSigninScreenExtension1UpdateManifestPath); |
| AddExtensionForForceInstallation(kSigninScreenExtension2, |
| kSigninScreenExtension2UpdateManifestPath); |
| |
| extension_1_observer.WaitForHostCompletedFirstLoad(); |
| extension_2_observer.WaitForHostCompletedFirstLoad(); |
| } |
| |
| content::StoragePartition* GetStoragePartitionForSigninExtension( |
| const std::string& extension_id) { |
| return extensions::util::GetStoragePartitionForExtensionId( |
| extension_id, signin_profile_, /*can_create=*/false); |
| } |
| |
| raw_ptr<Profile, ExperimentalAsh> signin_profile_ = nullptr; |
| scoped_refptr<net::X509Certificate> server_cert_; |
| |
| private: |
| // Builds an ONC policy value that specifies exactly one certificate described |
| // by |x509_contents| with Web trust to be used for |extension_id|. |
| base::Value::Dict BuildONCForExtensionScopedCertificate( |
| const std::string& x509_contents, |
| const std::string& extension_id) { |
| base::Value::Dict onc_cert_scope; |
| onc_cert_scope.Set(onc::scope::kType, onc::scope::kExtension); |
| onc_cert_scope.Set(onc::scope::kId, extension_id); |
| |
| base::Value::List onc_cert_trust_bits; |
| onc_cert_trust_bits.Append(onc::certificate::kWeb); |
| |
| base::Value::Dict onc_certificate; |
| onc_certificate.Set(onc::certificate::kGUID, base::Value("guid")); |
| onc_certificate.Set(onc::certificate::kType, onc::certificate::kAuthority); |
| onc_certificate.Set(onc::certificate::kX509, x509_contents); |
| onc_certificate.Set(onc::certificate::kScope, std::move(onc_cert_scope)); |
| onc_certificate.Set(onc::certificate::kTrustBits, |
| std::move(onc_cert_trust_bits)); |
| |
| base::Value::List onc_certificates; |
| onc_certificates.Append(std::move(onc_certificate)); |
| |
| base::Value::Dict onc_dict; |
| onc_dict.Set(onc::toplevel_config::kCertificates, |
| std::move(onc_certificates)); |
| onc_dict.Set(onc::toplevel_config::kType, |
| onc::toplevel_config::kUnencryptedConfiguration); |
| |
| return onc_dict; |
| } |
| }; // namespace policy |
| |
| // Verifies that a device-policy-provided, extension-scoped trust anchor is |
| // active only in the sign-in profile extension for which it was specified. |
| // Additionally verifies that it is not active |
| // (*) in the default StoragePartition of the sign-in profile, |
| // (*) in the StoragePartition used for the webview hosting GAIA and |
| // (*) in a different sign-in profile extension than the one for which it was |
| // specified. |
| // Verification of all these aspects has been intentionally put into one test, |
| // so if the verification result leaks (e.g. due to accidentally reusing |
| // caches), the test is able to catch that. |
| IN_PROC_BROWSER_TEST_F(PolicyProvidedCertsForSigninExtensionTest, |
| ActiveOnlyInSelectedExtension) { |
| ash::OobeScreenWaiter(ash::OobeBaseTest::GetFirstSigninScreen()).Wait(); |
| content::StoragePartition* signin_profile_default_partition = |
| signin_profile_->GetDefaultStoragePartition(); |
| |
| // Active in the StoragePartition of the extension for which the certificate |
| // has been specified in policy. |
| content::StoragePartition* extension_1_partition = |
| GetStoragePartitionForSigninExtension(kSigninScreenExtension1); |
| ASSERT_TRUE(extension_1_partition); |
| EXPECT_NE(signin_profile_default_partition, extension_1_partition); |
| EXPECT_EQ(net::OK, VerifyTestServerCertInStoragePartition( |
| extension_1_partition, server_cert_)); |
| |
| // Not active in default StoragePartition. |
| EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, |
| VerifyTestServerCertInStoragePartition( |
| signin_profile_default_partition, server_cert_)); |
| |
| // Not active in the StoragePartition used for the webview hosting GAIA. |
| content::StoragePartition* signin_frame_partition = |
| ash::login::GetSigninPartition(); |
| EXPECT_NE(signin_profile_default_partition, signin_frame_partition); |
| |
| EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, |
| VerifyTestServerCertInStoragePartition(signin_frame_partition, |
| server_cert_)); |
| |
| // Not active in the StoragePartition of another extension. |
| content::StoragePartition* extension_2_partition = |
| GetStoragePartitionForSigninExtension(kSigninScreenExtension2); |
| ASSERT_TRUE(extension_2_partition); |
| EXPECT_NE(signin_profile_default_partition, extension_2_partition); |
| |
| EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, |
| VerifyTestServerCertInStoragePartition(extension_2_partition, |
| server_cert_)); |
| } |
| |
| } // namespace policy |