Commit e5466217 authored by Yves Arrouye's avatar Yves Arrouye Committed by Commit Bot

Upload identification for enrollment as required by policy

Set a CrOS setting from cloud's device settings' forced re-enrollment.
Add an enrollment policy observer which observes the setting and
uploads an enterprise enrollment certificate when identification is
needed. This certificate will be used by the server to obtain the
device's enrollment ID for automatic forced re-enrollment.

Bug: 778535
Test: unit_tests
Change-Id: I7e95148e8101c27faa3dc1d21eb44971c79ee3c6
Reviewed-on: https://chromium-review.googlesource.com/758116
Commit-Queue: Yves Arrouye <drcrash@chromium.org>
Reviewed-by: default avatarMaksim Ivanov <emaxx@chromium.org>
Reviewed-by: default avatarAlexander Alekseev <alemate@chromium.org>
Cr-Commit-Position: refs/heads/master@{#515488}
parent 02da96a0
......@@ -443,6 +443,8 @@ source_set("chromeos") {
"attestation/attestation_ca_client.h",
"attestation/attestation_policy_observer.cc",
"attestation/attestation_policy_observer.h",
"attestation/enrollment_policy_observer.cc",
"attestation/enrollment_policy_observer.h",
"attestation/platform_verification_dialog.cc",
"attestation/platform_verification_dialog.h",
"attestation/platform_verification_flow.cc",
......@@ -1711,6 +1713,7 @@ source_set("unit_tests") {
"arc/wallpaper/arc_wallpaper_service_unittest.cc",
"attestation/attestation_ca_client_unittest.cc",
"attestation/attestation_policy_observer_unittest.cc",
"attestation/enrollment_policy_observer_unittest.cc",
"attestation/fake_certificate.cc",
"attestation/fake_certificate.h",
"attestation/platform_verification_flow_unittest.cc",
......
// 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 "chrome/browser/chromeos/attestation/enrollment_policy_observer.h"
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/location.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/attestation/attestation_ca_client.h"
#include "chrome/browser/chromeos/attestation/attestation_key_payload.pb.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chromeos/attestation/attestation_flow.h"
#include "chromeos/cryptohome/async_method_caller.h"
#include "chromeos/cryptohome/cryptohome_parameters.h"
#include "chromeos/dbus/cryptohome_client.h"
#include "chromeos/dbus/dbus_method_call_status.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/core/common/cloud/cloud_policy_manager.h"
#include "components/signin/core/account_id/account_id.h"
#include "components/user_manager/known_user.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_details.h"
#include "net/cert/pem_tokenizer.h"
#include "net/cert/x509_certificate.h"
namespace {
const int kRetryDelay = 5; // Seconds.
const int kRetryLimit = 100;
// A dbus callback which handles a string result.
//
// Parameters
// on_success - Called when status=success and result=true.
// status - The dbus operation status.
// result - The result returned by the dbus operation.
// data - The data returned by the dbus operation.
void DBusStringCallback(
const base::Callback<void(const std::string&)> on_success,
const base::Closure& on_failure,
const base::Location& from_here,
const chromeos::CryptohomeClient::TpmAttestationDataResult& result) {
if (!result.success) {
LOG(ERROR) << "Cryptohome DBus method failed: " << from_here.ToString();
if (!on_failure.is_null())
on_failure.Run();
return;
}
on_success.Run(result.data);
}
} // namespace
namespace chromeos {
namespace attestation {
EnrollmentPolicyObserver::EnrollmentPolicyObserver(
policy::CloudPolicyClient* policy_client)
: cros_settings_(CrosSettings::Get()),
policy_client_(policy_client),
cryptohome_client_(NULL),
attestation_flow_(NULL),
num_retries_(0),
retry_delay_(kRetryDelay),
weak_factory_(this) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
attestation_subscription_ = cros_settings_->AddSettingsObserver(
kDeviceEnrollmentIdNeeded,
base::Bind(&EnrollmentPolicyObserver::EnrollmentSettingChanged,
base::Unretained(this)));
Start();
}
EnrollmentPolicyObserver::EnrollmentPolicyObserver(
policy::CloudPolicyClient* policy_client,
CryptohomeClient* cryptohome_client,
AttestationFlow* attestation_flow)
: cros_settings_(CrosSettings::Get()),
policy_client_(policy_client),
cryptohome_client_(cryptohome_client),
attestation_flow_(attestation_flow),
num_retries_(0),
retry_delay_(kRetryDelay),
weak_factory_(this) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
attestation_subscription_ = cros_settings_->AddSettingsObserver(
kDeviceEnrollmentIdNeeded,
base::Bind(&EnrollmentPolicyObserver::EnrollmentSettingChanged,
base::Unretained(this)));
Start();
}
EnrollmentPolicyObserver::~EnrollmentPolicyObserver() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
void EnrollmentPolicyObserver::EnrollmentSettingChanged() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
num_retries_ = 0;
Start();
}
void EnrollmentPolicyObserver::Start() {
// If we identification for enrollment isn't needed, there is nothing to do.
bool needed = false;
if (!cros_settings_->GetBoolean(kDeviceEnrollmentIdNeeded, &needed) ||
!needed)
return;
// We expect a registered CloudPolicyClient.
if (!policy_client_->is_registered()) {
LOG(ERROR) << "EnrollmentPolicyObserver: Invalid CloudPolicyClient.";
return;
}
if (!cryptohome_client_)
cryptohome_client_ = DBusThreadManager::Get()->GetCryptohomeClient();
if (!attestation_flow_) {
std::unique_ptr<ServerProxy> attestation_ca_client(
new AttestationCAClient());
default_attestation_flow_.reset(new AttestationFlow(
cryptohome::AsyncMethodCaller::GetInstance(), cryptohome_client_,
std::move(attestation_ca_client)));
attestation_flow_ = default_attestation_flow_.get();
}
GetEnrollmentCertificate();
}
void EnrollmentPolicyObserver::GetEnrollmentCertificate() {
// We can reuse the dbus callback handler logic.
attestation_flow_->GetCertificate(
PROFILE_ENTERPRISE_ENROLLMENT_CERTIFICATE,
EmptyAccountId(), // Not used.
std::string(), // Not used.
false, // Not used.
base::Bind(
[](const base::Callback<void(const std::string&)> on_success,
const base::Closure& on_failure, const base::Location& from_here,
bool success, const std::string& data) {
DBusStringCallback(on_success, on_failure, from_here,
CryptohomeClient::TpmAttestationDataResult{
success, std::move(data)});
},
base::Bind(&EnrollmentPolicyObserver::UploadCertificate,
weak_factory_.GetWeakPtr()),
base::Bind(&EnrollmentPolicyObserver::Reschedule,
weak_factory_.GetWeakPtr()),
FROM_HERE));
}
void EnrollmentPolicyObserver::UploadCertificate(
const std::string& pem_certificate_chain) {
policy_client_->UploadCertificate(
pem_certificate_chain,
base::Bind(&EnrollmentPolicyObserver::OnUploadComplete,
weak_factory_.GetWeakPtr()));
}
void EnrollmentPolicyObserver::OnUploadComplete(bool status) {
if (!status)
return;
VLOG(1) << "Enterprise Enrollment Certificate uploaded to DMServer.";
cros_settings_->SetBoolean(kDeviceEnrollmentIdNeeded, false);
}
void EnrollmentPolicyObserver::Reschedule() {
if (++num_retries_ < kRetryLimit) {
content::BrowserThread::PostDelayedTask(
content::BrowserThread::UI, FROM_HERE,
base::BindOnce(&EnrollmentPolicyObserver::Start,
weak_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(retry_delay_));
} else {
LOG(WARNING) << "EnrollmentPolicyObserver: Retry limit exceeded.";
}
}
} // namespace attestation
} // namespace chromeos
// 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.
#ifndef CHROME_BROWSER_CHROMEOS_ATTESTATION_ENROLLMENT_POLICY_OBSERVER_H_
#define CHROME_BROWSER_CHROMEOS_ATTESTATION_ENROLLMENT_POLICY_OBSERVER_H_
#include <memory>
#include <string>
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
namespace policy {
class CloudPolicyClient;
}
namespace chromeos {
class CrosSettings;
class CryptohomeClient;
namespace attestation {
class AttestationFlow;
// A class which observes policy changes and triggers uploading identification
// for enrollment if necessary.
class EnrollmentPolicyObserver {
public:
// The observer immediately connects with CrosSettings to listen for policy
// changes. The CloudPolicyClient is used to upload data to the server; it
// must be in the registered state. This class does not take ownership of
// |policy_client|.
explicit EnrollmentPolicyObserver(policy::CloudPolicyClient* policy_client);
// A constructor which allows custom CryptohomeClient and AttestationFlow
// implementations. Useful for testing.
EnrollmentPolicyObserver(policy::CloudPolicyClient* policy_client,
CryptohomeClient* cryptohome_client,
AttestationFlow* attestation_flow);
~EnrollmentPolicyObserver();
// Sets the retry delay in seconds; useful in testing.
void set_retry_delay(int retry_delay) { retry_delay_ = retry_delay; }
private:
// Called when the enrollment setting changes.
void EnrollmentSettingChanged();
// Checks enrollment setting and starts any necessary work.
void Start();
// Gets an enrollment certificate.
void GetEnrollmentCertificate();
// Uploads a certificate to the policy server.
void UploadCertificate(const std::string& pem_certificate_chain);
// Called when a certificate upload operation completes. On success, |status|
// will be true.
void OnUploadComplete(bool status);
// Reschedules a policy check (i.e. a call to Start) for a later time.
// TODO(crbug.com/256845): A better solution would be to wait for a DBUS
// signal which indicates the system is ready to process this task.
void Reschedule();
CrosSettings* cros_settings_;
policy::CloudPolicyClient* policy_client_;
CryptohomeClient* cryptohome_client_;
AttestationFlow* attestation_flow_;
std::unique_ptr<AttestationFlow> default_attestation_flow_;
int num_retries_;
int retry_delay_;
std::unique_ptr<CrosSettings::ObserverSubscription> attestation_subscription_;
// Note: This should remain the last member so it'll be destroyed and
// invalidate the weak pointers before any other members are destroyed.
base::WeakPtrFactory<EnrollmentPolicyObserver> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(EnrollmentPolicyObserver);
};
} // namespace attestation
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_ATTESTATION_ENROLLMENT_POLICY_OBSERVER_H_
// Copyright (c) 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 <stdint.h>
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/chromeos/attestation/enrollment_policy_observer.h"
#include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
#include "chromeos/attestation/mock_attestation_flow.h"
#include "chromeos/dbus/fake_cryptohome_client.h"
#include "chromeos/settings/cros_settings_names.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::Invoke;
using testing::StrictMock;
using testing::WithArgs;
namespace chromeos {
namespace attestation {
namespace {
void CertCallbackSuccess(const AttestationFlow::CertificateCallback& callback) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, true, "fake_cert"));
}
void StatusCallbackSuccess(
const policy::CloudPolicyClient::StatusCallback& callback) {
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
base::BindOnce(callback, true));
}
} // namespace
class EnrollmentPolicyObserverTest : public ::testing::Test {
public:
EnrollmentPolicyObserverTest() {
settings_helper_.ReplaceProvider(kDeviceEnrollmentIdNeeded);
settings_helper_.SetBoolean(kDeviceEnrollmentIdNeeded, true);
policy_client_.SetDMToken("fake_dm_token");
}
protected:
// Configures mock expectations when identification for enrollment is needed.
void SetupMocks() {
EXPECT_CALL(attestation_flow_, GetCertificate(_, _, _, _, _))
.WillOnce(WithArgs<4>(Invoke(CertCallbackSuccess)));
EXPECT_CALL(policy_client_, UploadCertificate("fake_cert", _))
.WillOnce(WithArgs<1>(Invoke(StatusCallbackSuccess)));
}
void Run() {
EnrollmentPolicyObserver observer(&policy_client_, &cryptohome_client_,
&attestation_flow_);
observer.set_retry_delay(0);
base::RunLoop().RunUntilIdle();
}
content::TestBrowserThreadBundle test_browser_thread_bundle_;
ScopedCrosSettingsTestHelper settings_helper_;
FakeCryptohomeClient cryptohome_client_;
StrictMock<MockAttestationFlow> attestation_flow_;
StrictMock<policy::MockCloudPolicyClient> policy_client_;
};
TEST_F(EnrollmentPolicyObserverTest, FeatureDisabled) {
settings_helper_.SetBoolean(kDeviceEnrollmentIdNeeded, false);
Run();
}
TEST_F(EnrollmentPolicyObserverTest, UnregisteredPolicyClient) {
policy_client_.SetDMToken("");
Run();
}
TEST_F(EnrollmentPolicyObserverTest, DBusFailureRetry) {
SetupMocks();
// Simulate a DBus failure.
cryptohome_client_.SetServiceIsAvailable(false);
// Emulate delayed service initialization.
// Run() instantiates an Observer, which synchronously calls
// TpmAttestationDoesKeyExist() and fails. During this call, we make the
// service available in the next run, so on retry, it will successfully
// return the result.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(
[](FakeCryptohomeClient* cryptohome_client) {
cryptohome_client->SetServiceIsAvailable(true);
},
base::Unretained(&cryptohome_client_)));
Run();
}
} // namespace attestation
} // namespace chromeos
......@@ -22,6 +22,7 @@
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/attestation/attestation_policy_observer.h"
#include "chrome/browser/chromeos/attestation/enrollment_policy_observer.h"
#include "chrome/browser/chromeos/login/enrollment/auto_enrollment_controller.h"
#include "chrome/browser/chromeos/login/startup_utils.h"
#include "chrome/browser/chromeos/policy/device_cloud_policy_store_chromeos.h"
......@@ -264,6 +265,9 @@ void DeviceCloudPolicyManagerChromeOS::StartConnection(
core()->TrackRefreshDelayPref(local_state_,
prefs::kDevicePolicyRefreshRate);
enrollment_policy_observer_.reset(
new chromeos::attestation::EnrollmentPolicyObserver(client()));
// Don't start the AttestationPolicyObserver if machine cert requests
// are disabled.
if (!(base::CommandLine::ForCurrentProcess()->HasSwitch(
......
......@@ -28,6 +28,7 @@ class InstallAttributes;
namespace attestation {
class AttestationPolicyObserver;
class EnrollmentPolicyObserver;
}
}
......@@ -174,6 +175,8 @@ class DeviceCloudPolicyManagerChromeOS : public CloudPolicyManager {
// PrefService instance to read the policy refresh rate from.
PrefService* local_state_;
std::unique_ptr<chromeos::attestation::EnrollmentPolicyObserver>
enrollment_policy_observer_;
std::unique_ptr<chromeos::attestation::AttestationPolicyObserver>
attestation_policy_observer_;
......
......@@ -66,6 +66,7 @@ const char* const kKnownSettings[] = {
kDeviceAttestationEnabled,
kDeviceDisabled,
kDeviceDisabledMessage,
kDeviceEnrollmentIdNeeded,
kDeviceLoginScreenAppInstallList,
kDeviceOwner,
kDevicePrintersConfigurations,
......@@ -605,6 +606,15 @@ void DecodeGenericPolicies(
kCastReceiverName, base::MakeUnique<base::Value>(container.name()));
}
}
if (policy.has_forced_reenrollment()) {
const em::ForcedReenrollmentProto& container(policy.forced_reenrollment());
if (container.has_enrollment_id_needed()) {
new_values_cache->SetValue(
kDeviceEnrollmentIdNeeded,
base::MakeUnique<base::Value>(container.enrollment_id_needed()));
}
}
}
void DecodeLogUploadPolicies(const em::ChromeDeviceSettingsProto& policy,
......
......@@ -289,4 +289,8 @@ const char kMinimumRequiredChromeVersion[] = "cros.min_version.chrome";
// If the string is empty or blank the system name will be used.
const char kCastReceiverName[] = "cros.device.cast_receiver.name";
// Boolean indicating whether the client needs to upload an enrollment ID
// which can be used for automatic forced re-enrollment.
const char kDeviceEnrollmentIdNeeded[] = "cros.device.enrollment_id_needed";
} // namespace chromeos
......@@ -134,6 +134,8 @@ CHROMEOS_EXPORT extern const char kMinimumRequiredChromeVersion[];
CHROMEOS_EXPORT extern const char kCastReceiverName[];
CHROMEOS_EXPORT extern const char kDeviceEnrollmentIdNeeded[];
} // namespace chromeos
#endif // CHROMEOS_SETTINGS_CROS_SETTINGS_NAMES_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment