text: // Copyright 2017 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <algorithm>
  #include <tuple>
  #include <vector>
  
  #include "base/bind.h"
  #include "base/bind_helpers.h"
  #include "base/command_line.h"
  #include "base/json/json_writer.h"
  #include "base/run_loop.h"
  #include "base/threading/thread_restrictions.h"
  #include "chrome/browser/chromeos/arc/enterprise/cert_store/arc_cert_store_bridge.h"
  #include "chrome/browser/chromeos/arc/session/arc_service_launcher.h"
  #include "chrome/browser/chromeos/login/test/local_policy_test_server_mixin.h"
  #include "chrome/browser/chromeos/platform_keys/key_permissions/key_permissions.h"
  #include "chrome/browser/chromeos/platform_keys/platform_keys.h"
  #include "chrome/browser/chromeos/policy/user_policy_test_helper.h"
  #include "chrome/browser/chromeos/profiles/profile_helper.h"
  #include "chrome/browser/net/nss_context.h"
  #include "chrome/browser/policy/profile_policy_connector.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/ui/browser.h"
  #include "chrome/common/pref_names.h"
  #include "chrome/test/base/mixin_based_in_process_browser_test.h"
  #include "chrome/test/base/testing_profile.h"
  #include "chromeos/constants/chromeos_switches.h"
  #include "components/arc/arc_prefs.h"
  #include "components/arc/arc_service_manager.h"
  #include "components/arc/arc_util.h"
  #include "components/arc/mojom/cert_store.mojom.h"
  #include "components/arc/session/arc_bridge_service.h"
  #include "components/arc/test/connection_holder_util.h"
  #include "components/policy/policy_constants.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/test/browser_test.h"
  #include "crypto/scoped_test_system_nss_key_slot.h"
  #include "extensions/browser/extension_system.h"
  #include "net/cert/nss_cert_database.h"
  #include "net/cert/x509_util_nss.h"
  #include "net/test/cert_test_util.h"
  #include "net/test/test_data_directory.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  namespace {
  
  constexpr char kFakeUserName[] = "test@example.com";
  constexpr char kFakePackageName[] = "fake.package.name";
  constexpr char kFakeExtensionId[] = "fakeextensionid";
  
  // Returns true if cert1 < cert2.
  bool CertificatePtrLess(const arc::mojom::CertificatePtr& cert1,
                          const arc::mojom::CertificatePtr& cert2) {
    if (!cert1 || !cert2)
      return !cert2.is_null();
    return std::tie(cert1->alias, cert1->cert) <
           std::tie(cert2->alias, cert2->cert);
  }
  
  }  // namespace
  
  namespace arc {
  
  class FakeArcCertStoreInstance : public mojom::CertStoreInstance {
   public:
    // mojom::CertStoreInstance:
    void InitDeprecated(
        mojo::PendingRemote<mojom::CertStoreHost> host_remote) override {
      Init(std::move(host_remote), base::DoNothing());
    }
  
    void Init(mojo::PendingRemote<mojom::CertStoreHost> host_remote,
              InitCallback callback) override {
      host_remote_.reset();
      host_remote_.Bind(std::move(host_remote));
      std::move(callback).Run();
    }
  
    void OnKeyPermissionsChanged(
        const std::vector<std::string>& permissions) override {
      permissions_ = permissions;
    }
  
    void OnCertificatesChanged() override { is_on_certs_changed_called_ = true; }
  
    const std::vector<std::string>& permissions() const { return permissions_; }
    bool is_on_certs_changed_called() const {
      return is_on_certs_changed_called_;
    }
    void clear_on_certs_changed() { is_on_certs_changed_called_ = false; }
  
   private:
    mojo::Remote<mojom::CertStoreHost> host_remote_;
    std::vector<std::string> permissions_;
    bool is_on_certs_changed_called_ = false;
  };
  
  class ArcCertStoreBridgeTest : public MixinBasedInProcessBrowserTest {
   protected:
    ArcCertStoreBridgeTest() = default;
  
    // MixinBasedInProcessBrowserTest:
    void SetUpCommandLine(base::CommandLine* command_line) override {
      MixinBasedInProcessBrowserTest::SetUpCommandLine(command_line);
  
      arc::SetArcAvailableCommandLineForTesting(command_line);
  
      policy_helper_ = std::make_unique<policy::UserPolicyTestHelper>(
          kFakeUserName, &local_policy_server_);
      policy_helper_->SetPolicy(
          base::DictionaryValue() /* empty mandatory policy */,
          base::DictionaryValue() /* empty recommended policy */);
  
      command_line->AppendSwitchASCII(chromeos::switches::kLoginUser,
                                      kFakeUserName);
      command_line->AppendSwitchASCII(chromeos::switches::kLoginProfile,
                                      TestingProfile::kTestUserProfileDir);
      // Don't require policy for our sessions - this is required because
      // this test creates a secondary profile synchronously, so we need to
      // let the policy code know not to expect cached policy.
      command_line->AppendSwitchASCII(chromeos::switches::kProfileRequiresPolicy,
                                      "false");
  
      // Tell the policy subsystem to wait for an initial policy load, even
      // though we are using a synchronously loaded profile.
      // TODO(edmanp): Update this test to properly use an asynchronously loaded
      // user profile and remove the use of this flag (crbug.com/795737).
      command_line->AppendSwitchASCII(
          chromeos::switches::kWaitForInitialPolicyFetchForTest, "true");
    }
  
    void SetUpOnMainThread() override {
      MixinBasedInProcessBrowserTest::SetUpOnMainThread();
  
      policy_helper_->WaitForInitialPolicy(browser()->profile());
  
      // Init ArcSessionManager for testing.
      ArcServiceLauncher::Get()->ResetForTesting();
  
      chromeos::ProfileHelper::SetAlwaysReturnPrimaryUserForTesting(true);
  
      browser()->profile()->GetPrefs()->SetBoolean(prefs::kArcSignedIn, true);
      browser()->profile()->GetPrefs()->SetBoolean(prefs::kArcTermsAccepted,
                                                   true);
  
      ArcServiceLauncher::Get()->OnPrimaryUserProfilePrepared(
          browser()->profile());
  
      instance_ = std::make_unique<FakeArcCertStoreInstance>();
      ASSERT_TRUE(arc_bridge());
      arc_bridge()->cert_store()->SetInstance(instance_.get());
      WaitForInstanceReady(arc_bridge()->cert_store());
    }
  
    void TearDownOnMainThread() override {
      ASSERT_TRUE(arc_bridge());
      arc_bridge()->cert_store()->CloseInstance(instance_.get());
      instance_.reset();
  
      // Since ArcServiceLauncher is (re-)set up with profile() in
      // SetUpOnMainThread() it is necessary to Shutdown() before the profile()
      // is destroyed. ArcServiceLauncher::Shutdown() will be called again on
      // fixture destruction (because it is initialized with the original Profile
      // instance in fixture, once), but it should be no op.
      ArcServiceLauncher::Get()->Shutdown();
      chromeos::ProfileHelper::SetAlwaysReturnPrimaryUserForTesting(false);
      MixinBasedInProcessBrowserTest::TearDownOnMainThread();
    }
  
    ArcBridgeService* arc_bridge() {
      return ArcServiceManager::Get()->arc_bridge_service();
    }
  
    FakeArcCertStoreInstance* instance() { return instance_.get(); }
  
    // Set up the test policy that gives app the permission to access
    // corporate keys.
    void SetCorporateKeyUsagePolicy(const std::string& app_id) {
      base::DictionaryValue key_permissions_policy;
      {
        std::unique_ptr<base::DictionaryValue> cert_key_permission =
            std::make_unique<base::DictionaryValue>();
        cert_key_permission->SetKey("allowCorporateKeyUsage", base::Value(true));
        key_permissions_policy.SetWithoutPathExpansion(
            app_id, std::move(cert_key_permission));
      }
  
      std::string key_permissions_policy_str;
      base::JSONWriter::WriteWithOptions(key_permissions_policy,
                                         base::JSONWriter::OPTIONS_PRETTY_PRINT,
                                         &key_permissions_policy_str);
  
      base::DictionaryValue user_policy;
      user_policy.SetKey(policy::key::kKeyPermissions,
                         base::Value(key_permissions_policy_str));
  
      policy_helper_->SetPolicyAndWait(
          user_policy, base::DictionaryValue() /* empty recommended policy */,
          browser()->profile());
    }
  
    void RegisterCorporateKeys() {
      ASSERT_NO_FATAL_FAILURE(ImportCerts());
  
      policy::ProfilePolicyConnector* const policy_connector =
          browser()->profile()->GetProfilePolicyConnector();
  
      extensions::StateStore* const state_store =
          extensions::ExtensionSystem::Get(browser()->profile())->state_store();
  
      chromeos::platform_keys::KeyPermissions permissions(
          policy_connector->IsManaged(), browser()->profile()->GetPrefs(),
          policy_connector->policy_service(), state_store);
  
      {
        base::RunLoop run_loop;
        permissions.GetPermissionsForExtension(
            kFakeExtensionId,
            base::Bind(&ArcCertStoreBridgeTest::GotPermissionsForExtension,
                       base::Unretained(this), run_loop.QuitClosure()));
        run_loop.Run();
      }
    }
  
    std::vector<mojom::CertificatePtr> ListCertificates(
        ArcCertStoreBridge* cert_store_bridge) {
      std::vector<mojom::CertificatePtr> result;
      base::RunLoop loop;
      cert_store_bridge->ListCertificates(base::BindOnce(
          [](base::RunLoop* loop, std::vector<mojom::CertificatePtr>* result,
             std::vector<mojom::CertificatePtr> certs) {
            std::sort(certs.begin(), certs.end(), CertificatePtrLess);
            *result = std::move(certs);
            loop->Quit();
          },
          &loop, &result));
      loop.Run();
  
      return result;
    }
  
    // Imports certificates to NSS database.
    // FATAL ERROR: if the certificates were not imported properly.
    void ImportCerts() {
      base::RunLoop loop;
      GetNSSCertDatabaseForProfile(
          browser()->profile(),
          base::Bind(&ArcCertStoreBridgeTest::SetUpTestClientCerts,
                     base::Unretained(this), loop.QuitClosure()));
      loop.Run();
      // Certificates must be imported.
      ASSERT_NE(nullptr, client_cert1_);
    }
  
    net::ScopedCERTCertificate client_cert1_;
    net::ScopedCERTCertificate client_cert2_;
  
   private:
    // Register only client_cert1_ for corporate usage to test that
    // client_cert2_ is not allowed.
    void GotPermissionsForExtension(
        const base::Closure& done_callback,
        std::unique_ptr<
            chromeos::platform_keys::KeyPermissions::PermissionsForExtension>
            permissions_for_ext) {
      std::string client_cert1_spki(
          client_cert1_->derPublicKey.data,
          client_cert1_->derPublicKey.data + client_cert1_->derPublicKey.len);
      permissions_for_ext->RegisterKeyForCorporateUsage(
          client_cert1_spki, {chromeos::platform_keys::TokenId::kUser});
      done_callback.Run();
    }
  
    void SetUpTestClientCerts(const base::Closure& done_callback,
                              net::NSSCertDatabase* cert_db) {
      base::ScopedAllowBlockingForTesting allow_io;
      net::ImportSensitiveKeyFromFile(net::GetTestCertsDirectory(),
                                      "client_1.pk8",
                                      cert_db->GetPrivateSlot().get());
      net::ScopedCERTCertificateList certs =
          net::CreateCERTCertificateListFromFile(
              net::GetTestCertsDirectory(), "client_1.pem",
              net::X509Certificate::FORMAT_AUTO);
      EXPECT_EQ(1U, certs.size());
      if (certs.size() != 1U) {
        done_callback.Run();
        return;
      }
  
      client_cert1_ = net::x509_util::DupCERTCertificate(certs[0].get());
  
      // Import user certificate properly how it's done in PlatfromKeys.
      cert_db->ImportUserCert(client_cert1_.get());
  
      net::ImportClientCertAndKeyFromFile(
          net::GetTestCertsDirectory(), "client_2.pem", "client_2.pk8",
          cert_db->GetPrivateSlot().get(), &client_cert2_);
  
      done_callback.Run();
    }
  
    std::unique_ptr<policy::UserPolicyTestHelper> policy_helper_;
    std::unique_ptr<FakeArcCertStoreInstance> instance_;
    chromeos::LocalPolicyTestServerMixin local_policy_server_{&mixin_host_};
  
    DISALLOW_COPY_AND_ASSIGN(ArcCertStoreBridgeTest);
  };
  
  // Test OnKeyPermissionsChanged().
  IN_PROC_BROWSER_TEST_F(ArcCertStoreBridgeTest, KeyPermissionsTest) {
    ArcCertStoreBridge* cert_store_bridge =
        ArcCertStoreBridge::GetForBrowserContext(browser()->profile());
    ASSERT_TRUE(cert_store_bridge);
    // Corporate usage keys are not allowed to any app/extension:
    EXPECT_EQ(0U, instance()->permissions().size());
    EXPECT_FALSE(instance()->is_on_certs_changed_called());
  
    // Allow corporate usage keys to ARC app.
    SetCorporateKeyUsagePolicy(kFakePackageName);
  
    ASSERT_EQ(1U, instance()->permissions().size());
    EXPECT_EQ(kFakePackageName, instance()->permissions()[0]);
  
    EXPECT_TRUE(instance()->is_on_certs_changed_called());
    instance()->clear_on_certs_changed();
  
    // Allow corporate usage keys only to Chrome extensions.
    SetCorporateKeyUsagePolicy(kFakeExtensionId);
  
    EXPECT_EQ(0U, instance()->permissions().size());
    EXPECT_FALSE(instance()->is_on_certs_changed_called());
  }
  
  // Test ListCertificates() with no corporate usage keys.
  IN_PROC_BROWSER_TEST_F(ArcCertStoreBridgeTest, ListCertificatesBasicTest) {
    ArcCertStoreBridge* cert_store_bridge =
        ArcCertStoreBridge::GetForBrowserContext(browser()->profile());
    ASSERT_TRUE(cert_store_bridge);
  
    // Import certificates.
    ASSERT_NO_FATAL_FAILURE(ImportCerts());
  
    // Allow corporate usage keys to ARC app.
    SetCorporateKeyUsagePolicy(kFakePackageName);
  
    EXPECT_TRUE(ListCertificates(cert_store_bridge).empty());
  }
  
  // Test ListCertificates() with 2 corporate usage keys.
  IN_PROC_BROWSER_TEST_F(ArcCertStoreBridgeTest, ListCertificatesTest) {
    ArcCertStoreBridge* cert_store_bridge =
        ArcCertStoreBridge::GetForBrowserContext(browser()->profile());
    ASSERT_TRUE(cert_store_bridge);
  
    // Import and register corporate certificates.
    ASSERT_NO_FATAL_FAILURE(RegisterCorporateKeys());
    EXPECT_FALSE(instance()->is_on_certs_changed_called());
  
    // No ARC app is allowed to use corporate usage keys.
    EXPECT_TRUE(ListCertificates(cert_store_bridge).empty());
  
    // Allow corporate usage keys to ARC app.
    SetCorporateKeyUsagePolicy(kFakePackageName);
    auto mojom_cert1 = mojom::Certificate::New();
    mojom_cert1->alias = client_cert1_->nickname;
    auto x509_cert = net::x509_util::CreateX509CertificateFromCERTCertificate(
        client_cert1_.get());
    net::X509Certificate::GetPEMEncoded(x509_cert->cert_buffer(),
                                        &mojom_cert1->cert);
  
    std::vector<mojom::CertificatePtr> expected_certs;
    expected_certs.emplace_back(std::move(mojom_cert1));
  
    const std::vector<mojom::CertificatePtr>& certs =
        ListCertificates(cert_store_bridge);
  
    ASSERT_EQ(expected_certs.size(), certs.size());
    for (size_t i = 0; i < certs.size(); ++i) {
      EXPECT_EQ(expected_certs[i]->alias, certs[i]->alias);
      EXPECT_EQ(expected_certs[i]->cert, certs[i]->cert);
    }
  }
  
  IN_PROC_BROWSER_TEST_F(ArcCertStoreBridgeTest, OnCertificatesChangedTest) {
    ArcCertStoreBridge* cert_store_bridge =
        ArcCertStoreBridge::GetForBrowserContext(browser()->profile());
    ASSERT_TRUE(cert_store_bridge);
  
    // Allow corporate usage keys to ARC app.
    SetCorporateKeyUsagePolicy(kFakePackageName);
    instance()->clear_on_certs_changed();
  
    // Import and register corporate certificates.
    ASSERT_NO_FATAL_FAILURE(RegisterCorporateKeys());
  
    EXPECT_TRUE(instance()->is_on_certs_changed_called());
  }
  
  }  // namespace arc
  