blob: 742ed5c41303a40721f91bbd6adc78fb03a043f8 [file] [log] [blame]
// Copyright 2014 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 <utility>
#include "ash/constants/ash_switches.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/files/dir_reader_posix.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/values.h"
#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/policy/core/device_cloud_policy_store_ash.h"
#include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
#include "chrome/browser/ash/settings/device_settings_service.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h"
#include "components/ownership/owner_key_util.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/core/common/cloud/device_management_service.h"
#include "components/policy/core/common/cloud/test/policy_builder.h"
#include "components/policy/core/common/policy_service.h"
#include "components/policy/core/common/policy_switches.h"
#include "components/policy/policy_constants.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "components/policy/proto/chrome_extension_policy.pb.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "content/public/test/browser_test.h"
#include "crypto/rsa_private_key.h"
#include "crypto/sha2.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/updater/extension_downloader_test_helper.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/switches.h"
#include "extensions/test/result_catcher.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
namespace policy {
namespace {
// Tests for the rotation of the signing keys used for the device policy.
//
// The test is performed against a test policy server, which is set up for
// rotating the policy key automatically with each policy fetch.
class KeyRotationDeviceCloudPolicyTest : public DevicePolicyCrosBrowserTest {
public:
KeyRotationDeviceCloudPolicyTest(const KeyRotationDeviceCloudPolicyTest&) =
delete;
KeyRotationDeviceCloudPolicyTest& operator=(
const KeyRotationDeviceCloudPolicyTest&) = delete;
protected:
const int kInitialPolicyValue = 123;
const int kSecondPolicyValue = 456;
const int kThirdPolicyValue = 789;
const char* const kPolicyKey = key::kDevicePolicyRefreshRate;
KeyRotationDeviceCloudPolicyTest() {
UpdateBuiltTestPolicyValue(kInitialPolicyValue);
}
void SetUpInProcessBrowserTestFixture() override {
DevicePolicyCrosBrowserTest::SetUpInProcessBrowserTestFixture();
SetFakeDevicePolicy();
UpdateServedTestPolicy();
}
void SetUpOnMainThread() override {
DevicePolicyCrosBrowserTest::SetUpOnMainThread();
StartObservingTestPolicy();
}
void TearDownOnMainThread() override {
policy_change_registrar_.reset();
DevicePolicyCrosBrowserTest::TearDownOnMainThread();
}
void UpdateBuiltTestPolicyValue(int test_policy_value) {
device_policy()
->payload()
.mutable_device_policy_refresh_rate()
->set_device_policy_refresh_rate(test_policy_value);
device_policy()->Build();
}
void UpdateServedTestPolicy() {
policy_test_server_mixin_.UpdateDevicePolicy(device_policy()->payload());
}
void StartDevicePolicyRefresh() {
g_browser_process->platform_part()
->browser_policy_connector_ash()
->GetDeviceCloudPolicyManager()
->RefreshPolicies();
}
std::string GetOwnerPublicKey() const {
return ash::DeviceSettingsService::Get()->GetPublicKey()->as_string();
}
int GetInstalledPolicyKeyVersion() const {
return g_browser_process->platform_part()
->browser_policy_connector_ash()
->GetDeviceCloudPolicyManager()
->device_store()
->policy()
->public_key_version();
}
int GetInstalledPolicyValue() {
PolicyService* const policy_service = g_browser_process->platform_part()
->browser_policy_connector_ash()
->GetPolicyService();
const base::Value* policy_value =
policy_service
->GetPolicies(PolicyNamespace(POLICY_DOMAIN_CHROME,
std::string() /* component_id */))
.GetValue(kPolicyKey, base::Value::Type::INTEGER);
EXPECT_TRUE(policy_value);
EXPECT_EQ(policy_value->type(), base::Value::Type::INTEGER);
return policy_value->GetIfInt().value_or(-1);
}
void WaitForInstalledPolicyValue(int expected_policy_value) {
if (GetInstalledPolicyValue() == expected_policy_value)
return;
awaited_policy_value_ = expected_policy_value;
// The run loop will be terminated by OnPolicyChanged() once the policy
// value becomes equal to the awaited value.
DCHECK(!policy_change_waiting_run_loop_);
policy_change_waiting_run_loop_ = std::make_unique<base::RunLoop>();
policy_change_waiting_run_loop_->Run();
policy_change_waiting_run_loop_.reset();
}
private:
void SetFakeDevicePolicy() {
device_policy()
->payload()
.mutable_device_policy_refresh_rate()
->set_device_policy_refresh_rate(kInitialPolicyValue);
device_policy()->Build();
session_manager_client()->set_device_policy(device_policy()->GetBlob());
}
void StartObservingTestPolicy() {
policy_change_registrar_ = std::make_unique<PolicyChangeRegistrar>(
g_browser_process->platform_part()
->browser_policy_connector_ash()
->GetPolicyService(),
PolicyNamespace(POLICY_DOMAIN_CHROME,
std::string() /* component_id */));
policy_change_registrar_->Observe(
kPolicyKey,
base::BindRepeating(&KeyRotationDeviceCloudPolicyTest::OnPolicyChanged,
base::Unretained(this)));
}
void OnPolicyChanged(const base::Value* old_value,
const base::Value* new_value) {
if (policy_change_waiting_run_loop_ &&
GetInstalledPolicyValue() == awaited_policy_value_) {
policy_change_waiting_run_loop_->Quit();
}
}
ash::EmbeddedPolicyTestServerMixin policy_test_server_mixin_{
&mixin_host_,
{ash::EmbeddedPolicyTestServerMixin::ENABLE_CANNED_SIGNING_KEYS,
ash::EmbeddedPolicyTestServerMixin::
ENABLE_AUTOMATIC_ROTATION_OF_SIGNINGKEYS}};
std::unique_ptr<PolicyChangeRegistrar> policy_change_registrar_;
int awaited_policy_value_ = -1;
std::unique_ptr<base::RunLoop> policy_change_waiting_run_loop_;
};
} // namespace
IN_PROC_BROWSER_TEST_F(KeyRotationDeviceCloudPolicyTest, Basic) {
// The policy has the initial value from the cache.
EXPECT_EQ(kInitialPolicyValue, GetInstalledPolicyValue());
// The server is updated to serve the second policy value, and the client
// fetches it.
UpdateBuiltTestPolicyValue(kSecondPolicyValue);
UpdateServedTestPolicy();
StartDevicePolicyRefresh();
WaitForInstalledPolicyValue(kSecondPolicyValue);
EXPECT_EQ(kSecondPolicyValue, GetInstalledPolicyValue());
// Remember the key and the version after the fetch of the second value.
const std::string owner_public_key = GetOwnerPublicKey();
CHECK(!owner_public_key.empty());
const int key_version = GetInstalledPolicyKeyVersion();
// The server is updated to serve the third policy value, and the client
// fetches it.
UpdateBuiltTestPolicyValue(kThirdPolicyValue);
UpdateServedTestPolicy();
StartDevicePolicyRefresh();
WaitForInstalledPolicyValue(kThirdPolicyValue);
EXPECT_EQ(kThirdPolicyValue, GetInstalledPolicyValue());
// The owner key got rotated on the client, as requested by the server, and
// the key version got incremented.
EXPECT_NE(owner_public_key, GetOwnerPublicKey());
EXPECT_EQ(key_version + 1, GetInstalledPolicyKeyVersion());
}
namespace {
// Tests how component policy is handled for extensions installed on the sign-in
// screen.
class SigninExtensionsDeviceCloudPolicyBrowserTest
: public DevicePolicyCrosBrowserTest {
public:
static constexpr const char* kTestExtensionId =
"hifnmfgfdfhmoaponfpmnlpeahiomjim";
static constexpr const char* kTestExtensionPath =
"extensions/signin_screen_managed_storage/extension.crx";
static constexpr const char* kTestExtensionUpdateManifestPath =
"/extensions/signin_screen_managed_storage/update_manifest.xml";
static constexpr const char* kFakePolicyPath = "/test-policy.json";
static constexpr const char* kFakePolicy =
"{\"string-policy\": {\"Value\": \"value\"}}";
static constexpr int kFakePolicyPublicKeyVersion = 1;
SigninExtensionsDeviceCloudPolicyBrowserTest() = default;
SigninExtensionsDeviceCloudPolicyBrowserTest(
const SigninExtensionsDeviceCloudPolicyBrowserTest&) = delete;
SigninExtensionsDeviceCloudPolicyBrowserTest& operator=(
const SigninExtensionsDeviceCloudPolicyBrowserTest&) = delete;
~SigninExtensionsDeviceCloudPolicyBrowserTest() override = default;
void SetUp() override {
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
DevicePolicyCrosBrowserTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
DevicePolicyCrosBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(ash::switches::kLoginManager);
command_line->AppendSwitch(ash::switches::kForceLoginManagerInTests);
// The test app has to be allowlisted for sign-in screen.
command_line->AppendSwitchASCII(
extensions::switches::kAllowlistedExtensionID, kTestExtensionId);
}
void SetUpInProcessBrowserTestFixture() override {
DevicePolicyCrosBrowserTest::SetUpInProcessBrowserTestFixture();
SetFakeDevicePolicy();
policy_test_server_mixin_.UpdateDevicePolicy(device_policy()->payload());
policy_test_server_mixin_.UpdatePolicy(
dm_protocol::kChromeSigninExtensionPolicyType, kTestExtensionId,
BuildTestComponentPolicyPayload().SerializeAsString());
}
void SetUpOnMainThread() override {
DevicePolicyCrosBrowserTest::SetUpOnMainThread();
BrowserPolicyConnectorAsh* connector =
g_browser_process->platform_part()->browser_policy_connector_ash();
connector->device_management_service()->ScheduleInitialization(0);
}
// |hang_component_policy_fetch| - whether requests for the component policy
// download should be hung indefinitely.
void StartTestServer(bool hang_component_policy_fetch) {
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&SigninExtensionsDeviceCloudPolicyBrowserTest::InterceptComponentPolicy,
base::Unretained(this), hang_component_policy_fetch));
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&SigninExtensionsDeviceCloudPolicyBrowserTest::InterceptUpdateManifest,
base::Unretained(this)));
embedded_test_server()->StartAcceptingConnections();
}
private:
// Intercepts the request for the test extension update manifest.
std::unique_ptr<net::test_server::HttpResponse> InterceptUpdateManifest(
const net::test_server::HttpRequest& request) {
if (request.GetURL().path() != kTestExtensionUpdateManifestPath)
return nullptr;
// Create update manifest for the test extension, setting the extension URL
// with a test server URL pointing to the extension under the test data
// path.
std::string manifest_response = extensions::CreateUpdateManifest(
{extensions::UpdateManifestItem(kTestExtensionId)
.version("1.0")
.codebase(base::ReplaceStringPlaceholders(
"http://$1/$2",
{embedded_test_server()->host_port_pair().ToString(),
kTestExtensionPath},
nullptr))});
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->set_content_type("text/xml");
response->set_content(manifest_response);
return response;
}
// Intercepts the component policy requests.
// |hang| - if set, this will return a hung response, thus preventing the
// policy download. Otherwise, the response will contain the test policy.
std::unique_ptr<net::test_server::HttpResponse> InterceptComponentPolicy(
bool hang,
const net::test_server::HttpRequest& request) {
if (request.relative_url != kFakePolicyPath)
return nullptr;
if (hang)
return std::make_unique<net::test_server::HungResponse>();
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->set_content(kFakePolicy);
return response;
}
void SetFakeDevicePolicy() {
device_policy()->policy_data().set_public_key_version(
kFakePolicyPublicKeyVersion);
const GURL update_manifest_url =
embedded_test_server()->GetURL(kTestExtensionUpdateManifestPath);
const std::string policy_item_value = base::ReplaceStringPlaceholders(
"$1;$2", {kTestExtensionId, update_manifest_url.spec()}, nullptr);
device_policy()
->payload()
.mutable_device_login_screen_extensions()
->add_device_login_screen_extensions(policy_item_value);
device_policy()->Build();
session_manager_client()->set_device_policy(device_policy()->GetBlob());
}
enterprise_management::ExternalPolicyData BuildTestComponentPolicyPayload() {
ComponentCloudPolicyBuilder builder;
MakeTestComponentPolicyBuilder(&builder);
return builder.payload();
}
void MakeTestComponentPolicyBuilder(ComponentCloudPolicyBuilder* builder) {
builder->policy_data().set_policy_type(
dm_protocol::kChromeSigninExtensionPolicyType);
builder->policy_data().set_settings_entity_id(kTestExtensionId);
builder->policy_data().set_public_key_version(kFakePolicyPublicKeyVersion);
builder->payload().set_download_url(
embedded_test_server()->GetURL(kFakePolicyPath).spec());
builder->payload().set_secure_hash(crypto::SHA256HashString(kFakePolicy));
builder->Build();
}
ash::EmbeddedPolicyTestServerMixin policy_test_server_mixin_{&mixin_host_};
};
} // namespace
// The ManagedStorage test is done in two steps:
// 1. Test that fetches the component policy and verifies that the fetched
// policy is exposed to a test extension installed into the sign-in profile.
// 2. Test that blocks the component policy fetch, and verifies that a test
// extension installed into the sign-in profile can access the component
// policy downloaded during the first step.
// PRE_ManagedStorage test handles the first step.
IN_PROC_BROWSER_TEST_F(SigninExtensionsDeviceCloudPolicyBrowserTest,
PRE_ManagedStorage) {
// The test app will be installed via policy, at which point its
// background page will be loaded.
extensions::ResultCatcher result_catcher;
StartTestServer(false /*hang_component_policy_fetch*/);
EXPECT_TRUE(result_catcher.GetNextResult());
}
// The second step of the ManagedStorage test, which blocks component policy
// download and verifies that a cached component policy is available to the test
// extenion.
// See PRE_ManagedStorage test.
IN_PROC_BROWSER_TEST_F(SigninExtensionsDeviceCloudPolicyBrowserTest,
ManagedStorage) {
// The test app will be installed via policy, at which point its
// background page will be loaded. Note that the app will not be installed
// before the test server is started, even if the app is installed from the
// extension cache - the server will be pinged at least to check whether the
// cached app version is the latest.
extensions::ResultCatcher result_catcher;
StartTestServer(true /*hang_component_policy_fetch*/);
EXPECT_TRUE(result_catcher.GetNextResult());
}
} // namespace policy