blob: 7df6808281ab075c19d8a0ec961d148da2a80bb7 [file] [log] [blame]
// 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.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 "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(mojom::CertStoreHostPtr host) override {
Init(std::move(host), base::DoNothing());
}
void Init(mojom::CertStoreHostPtr host, InitCallback callback) override {
host_ = std::move(host);
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:
mojom::CertStoreHostPtr host_;
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::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::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::KeyPermissions::KeyLocation::kUserSlot});
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