| // Copyright 2014 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/login/enrollment/auto_enrollment_controller.h" |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chromeos/policy/auto_enrollment_client_impl.h" |
| #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" |
| #include "chrome/browser/chromeos/policy/server_backed_state_keys_broker.h" |
| #include "chrome/browser/net/system_network_context_manager.h" |
| #include "chromeos/chromeos_switches.h" |
| #include "chromeos/dbus/cryptohome/rpc.pb.h" |
| #include "chromeos/dbus/cryptohome_client.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/system_clock_client.h" |
| #include "chromeos/system/factory_ping_embargo_check.h" |
| #include "chromeos/system/statistics_provider.h" |
| #include "components/policy/core/common/cloud/device_management_service.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| // Maximum number of bits of the identifer hash to send during initial |
| // enrollment check. |
| constexpr int kInitialEnrollmentModulusPowerLimit = 6; |
| |
| // If the modulus requested by the server is higher or equal to |
| // |1<<kInitialEnrollmentModulusPowerOutdatedServer|, assume that the server |
| // does not know initial enrollment yet. |
| // This is currently set to |14|, the server was requesting |16| for FRE on |
| // 2018-05-25. |
| // TODO(pmarko): Remove this mechanism when the server version supporting |
| // Initial Enrollment has been in production for a while |
| // (https://crbug.com/846645). |
| const int kInitialEnrollmentModulusPowerOutdatedServer = 14; |
| |
| // Maximum time to wait for the auto-enrollment check to reach a decision. |
| // Note that this encompasses all steps |AutoEnrollmentController| performs in |
| // order to determine if the device should be auto-enrolled. |
| // If |kSafeguardTimeout| after |Start()| has been called, |
| // |AutoEnrollmentController::state()| is still AUTO_ENROLLMENT_STATE_PENDING, |
| // the AutoEnrollmentController will switch to |
| // AUTO_ENROLLMENT_STATE_NO_ENROLLMENT or AUTO_ENROLLMENT_STATE_CONNECTION_ERROR |
| // (see |AutoEnrollmentController::Timeout|). Note that this timeout should not |
| // be too short, because one of the steps |AutoEnrollmentController| performs - |
| // downloading identifier hash buckets - can be non-negligible, especially on 2G |
| // connections. |
| constexpr base::TimeDelta kSafeguardTimeout = base::TimeDelta::FromSeconds(90); |
| |
| // Maximum time to wait for time sync before forcing a decision on whether |
| // Initial Enrollment should be performed. |
| constexpr base::TimeDelta kSystemClockSyncWaitTimeout = |
| base::TimeDelta::FromSeconds(15); |
| |
| // A callback that will be invoked when the system clock has been synchronized, |
| // or if system clock synchronization has failed. |
| using SystemClockSyncCallback = base::OnceCallback<void( |
| AutoEnrollmentController::SystemClockSyncState system_clock_sync_state)>; |
| |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| // These values must match the corresponding enum defined in enums.xml. |
| enum class InitialEnrollmentRequirementHistogramValue { |
| kRequired = 0, |
| kNotRequiredSerialNumberMissing = 1, |
| kNotRequiredBrandCodeMissing = 2, |
| kNotRequiredEmbargoEndDateInvalid = 3, |
| kNotRequiredEmbargoEndDateInvalidWithoutSystemClockSync = 4, |
| kNotRequiredInEmbargoPeriod = 5, |
| kNotRequiredInEmbargoPeriodWithoutSystemClockSync = 6, |
| kMaxValue = kNotRequiredInEmbargoPeriodWithoutSystemClockSync |
| }; |
| |
| // Returns the int value of the |switch_name| argument, clamped to the [0, 62] |
| // interval. Returns 0 if the argument doesn't exist or isn't an int value. |
| int GetSanitizedArg(const std::string& switch_name) { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| if (!command_line->HasSwitch(switch_name)) |
| return 0; |
| std::string value = command_line->GetSwitchValueASCII(switch_name); |
| int int_value; |
| if (!base::StringToInt(value, &int_value)) { |
| LOG(ERROR) << "Switch \"" << switch_name << "\" is not a valid int. " |
| << "Defaulting to 0."; |
| return 0; |
| } |
| if (int_value < 0) { |
| LOG(ERROR) << "Switch \"" << switch_name << "\" can't be negative. " |
| << "Using 0"; |
| return 0; |
| } |
| if (int_value > policy::AutoEnrollmentClient::kMaximumPower) { |
| LOG(ERROR) << "Switch \"" << switch_name << "\" can't be greater than " |
| << policy::AutoEnrollmentClient::kMaximumPower << ". Using " |
| << policy::AutoEnrollmentClient::kMaximumPower; |
| return policy::AutoEnrollmentClient::kMaximumPower; |
| } |
| return int_value; |
| } |
| |
| std::string FRERequirementToString( |
| AutoEnrollmentController::FRERequirement requirement) { |
| using FRERequirement = AutoEnrollmentController::FRERequirement; |
| switch (requirement) { |
| case FRERequirement::kRequired: |
| return "Auto-enrollment required."; |
| case FRERequirement::kNotRequired: |
| return "Auto-enrollment disabled: first setup."; |
| case FRERequirement::kExplicitlyRequired: |
| return "Auto-enrollment required: flag in VPD."; |
| case FRERequirement::kExplicitlyNotRequired: |
| return "Auto-enrollment disabled: flag in VPD."; |
| } |
| |
| NOTREACHED(); |
| return std::string(); |
| } |
| |
| // Returns true if this is an official build and the device has Chrome firmware. |
| bool IsOfficialChrome() { |
| std::string firmware_type; |
| bool is_official = |
| !system::StatisticsProvider::GetInstance()->GetMachineStatistic( |
| system::kFirmwareTypeKey, &firmware_type) || |
| firmware_type != system::kFirmwareTypeValueNonchrome; |
| #if !defined(OFFICIAL_BUILD) |
| is_official = false; |
| #endif |
| return is_official; |
| } |
| |
| // Schedules immediate initialization of the |DeviceManagementService| and |
| // returns it. |
| policy::DeviceManagementService* InitializeAndGetDeviceManagementService() { |
| policy::BrowserPolicyConnectorChromeOS* connector = |
| g_browser_process->platform_part()->browser_policy_connector_chromeos(); |
| policy::DeviceManagementService* service = |
| connector->device_management_service(); |
| service->ScheduleInitialization(0); |
| return service; |
| } |
| |
| } // namespace |
| |
| // Supports waiting for the system clock to become synchronized. |
| class AutoEnrollmentController::SystemClockSyncWaiter |
| : public chromeos::SystemClockClient::Observer { |
| public: |
| SystemClockSyncWaiter() : weak_ptr_factory_(this) { |
| chromeos::DBusThreadManager::Get()->GetSystemClockClient()->AddObserver( |
| this); |
| } |
| |
| ~SystemClockSyncWaiter() override { |
| chromeos::DBusThreadManager::Get()->GetSystemClockClient()->RemoveObserver( |
| this); |
| } |
| |
| // Waits for the system clock to be synchronized. If it already is |
| // synchronized, |callback| will be called immediately. Otherwise, |callback| |
| // will be called when the system clock has been synchronized, or after |
| // |kSystemClockSyncWaitTimeout|. |
| void WaitForSystemClockSync(SystemClockSyncCallback callback) { |
| if (state_ == SystemClockSyncState::kSyncFailed || |
| state_ == SystemClockSyncState::kSynchronized) { |
| std::move(callback).Run(state_); |
| return; |
| } |
| |
| system_clock_sync_callbacks_.push_back(std::move(callback)); |
| |
| if (state_ == SystemClockSyncState::kWaitingForSync) |
| return; |
| state_ = SystemClockSyncState::kWaitingForSync; |
| |
| timeout_timer_.Start(FROM_HERE, kSystemClockSyncWaitTimeout, |
| base::BindRepeating(&SystemClockSyncWaiter::OnTimeout, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| chromeos::DBusThreadManager::Get() |
| ->GetSystemClockClient() |
| ->WaitForServiceToBeAvailable(base::BindOnce( |
| &SystemClockSyncWaiter::OnGotSystemClockServiceAvailable, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| private: |
| // Called when the system clock D-Bus service is available, or when it is |
| // known that the system clock D-Bus service is not available. |
| void OnGotSystemClockServiceAvailable(bool service_is_available) { |
| if (!service_is_available) { |
| SetStateAndRunCallbacks(SystemClockSyncState::kSyncFailed); |
| return; |
| } |
| |
| chromeos::DBusThreadManager::Get()->GetSystemClockClient()->GetLastSyncInfo( |
| base::BindOnce(&SystemClockSyncWaiter::OnGotLastSyncInfo, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| // Called on initial fetch of the system clock sync state, and when the system |
| // clock sync state has changed. |
| void OnGotLastSyncInfo(bool network_synchronized) { |
| if (!network_synchronized) |
| return; |
| |
| SetStateAndRunCallbacks(SystemClockSyncState::kSynchronized); |
| } |
| |
| // Called when the time out has been reached. |
| void OnTimeout() { |
| SetStateAndRunCallbacks(SystemClockSyncState::kSyncFailed); |
| } |
| |
| // Runs all callbacks in |system_clock_sync_callbacks_| and clears the vector. |
| void SetStateAndRunCallbacks(SystemClockSyncState state) { |
| state_ = state; |
| timeout_timer_.AbandonAndStop(); |
| |
| std::vector<SystemClockSyncCallback> callbacks; |
| callbacks.swap(system_clock_sync_callbacks_); |
| for (auto& callback : callbacks) { |
| std::move(callback).Run(state_); |
| } |
| } |
| |
| // chromeos::SystemClockClient::Observer: |
| void SystemClockUpdated() override { |
| chromeos::DBusThreadManager::Get()->GetSystemClockClient()->GetLastSyncInfo( |
| base::BindOnce(&SystemClockSyncWaiter::OnGotLastSyncInfo, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| // Current state of the system clock. |
| SystemClockSyncState state_ = SystemClockSyncState::kCanWaitForSync; |
| |
| // Pending callbacks to be called when the system clock has been synchronized |
| // or a timeout has been reached. |
| std::vector<SystemClockSyncCallback> system_clock_sync_callbacks_; |
| |
| base::OneShotTimer timeout_timer_; |
| |
| base::WeakPtrFactory<SystemClockSyncWaiter> weak_ptr_factory_{this}; |
| |
| DISALLOW_COPY_AND_ASSIGN(SystemClockSyncWaiter); |
| }; |
| |
| namespace { |
| |
| // Records the "Enterprise.InitialEnrollmentRequirement" histogram value. |
| // Do not pass |*WithoutSystemClockSync| enum values as |value|. |
| // If |value| is one of the values that are only generated at specific system |
| // clock values (that is, related to the factory ping embargo period), |
| // |system_clock_sync_state| is used to determine if the reported value should |
| // be |value| or the corresponding |*WithoutSystemClockSync| value. |
| void RecordInitialEnrollmentRequirement( |
| InitialEnrollmentRequirementHistogramValue value, |
| AutoEnrollmentController::SystemClockSyncState system_clock_sync_state) { |
| DCHECK_NE(value, InitialEnrollmentRequirementHistogramValue:: |
| kNotRequiredEmbargoEndDateInvalidWithoutSystemClockSync); |
| DCHECK_NE(value, InitialEnrollmentRequirementHistogramValue:: |
| kNotRequiredInEmbargoPeriodWithoutSystemClockSync); |
| if (system_clock_sync_state != |
| AutoEnrollmentController::SystemClockSyncState::kSynchronized) { |
| if (value == InitialEnrollmentRequirementHistogramValue:: |
| kNotRequiredEmbargoEndDateInvalid) { |
| value = InitialEnrollmentRequirementHistogramValue:: |
| kNotRequiredEmbargoEndDateInvalidWithoutSystemClockSync; |
| } |
| if (value == InitialEnrollmentRequirementHistogramValue:: |
| kNotRequiredInEmbargoPeriod) { |
| value = InitialEnrollmentRequirementHistogramValue:: |
| kNotRequiredInEmbargoPeriodWithoutSystemClockSync; |
| } |
| } |
| UMA_HISTOGRAM_ENUMERATION("Enterprise.InitialEnrollmentRequirement", value); |
| } |
| |
| } // namespace |
| |
| const char AutoEnrollmentController::kForcedReEnrollmentAlways[] = "always"; |
| const char AutoEnrollmentController::kForcedReEnrollmentNever[] = "never"; |
| const char AutoEnrollmentController::kForcedReEnrollmentOfficialBuild[] = |
| "official"; |
| |
| const char AutoEnrollmentController::kInitialEnrollmentAlways[] = "always"; |
| const char AutoEnrollmentController::kInitialEnrollmentNever[] = "never"; |
| const char AutoEnrollmentController::kInitialEnrollmentOfficialBuild[] = |
| "official"; |
| |
| // static |
| bool AutoEnrollmentController::IsFREEnabled() { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| |
| std::string command_line_mode = command_line->GetSwitchValueASCII( |
| switches::kEnterpriseEnableForcedReEnrollment); |
| if (command_line_mode == kForcedReEnrollmentAlways) |
| return true; |
| |
| if (command_line_mode.empty() || |
| command_line_mode == kForcedReEnrollmentOfficialBuild) { |
| return IsOfficialChrome(); |
| } |
| |
| if (command_line_mode == kForcedReEnrollmentNever) |
| return false; |
| |
| LOG(FATAL) << "Unknown auto-enrollment mode for FRE " << command_line_mode; |
| return false; |
| } |
| |
| // static |
| bool AutoEnrollmentController::IsInitialEnrollmentEnabled() { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| |
| if (!command_line->HasSwitch(switches::kEnterpriseEnableInitialEnrollment)) |
| return IsOfficialChrome(); |
| |
| std::string command_line_mode = command_line->GetSwitchValueASCII( |
| switches::kEnterpriseEnableInitialEnrollment); |
| if (command_line_mode == kInitialEnrollmentAlways) |
| return true; |
| |
| if (command_line_mode.empty() || |
| command_line_mode == kInitialEnrollmentOfficialBuild) { |
| return IsOfficialChrome(); |
| } |
| |
| if (command_line_mode == kInitialEnrollmentNever) |
| return false; |
| |
| LOG(FATAL) << "Unknown auto-enrollment mode for initial enrollment " |
| << command_line_mode; |
| return false; |
| } |
| |
| // static |
| bool AutoEnrollmentController::IsEnabled() { |
| return IsFREEnabled() || IsInitialEnrollmentEnabled(); |
| } |
| |
| // static |
| AutoEnrollmentController::FRERequirement |
| AutoEnrollmentController::GetFRERequirement() { |
| std::string check_enrollment_value; |
| system::StatisticsProvider* provider = |
| system::StatisticsProvider::GetInstance(); |
| bool fre_flag_found = provider->GetMachineStatistic( |
| system::kCheckEnrollmentKey, &check_enrollment_value); |
| |
| if (fre_flag_found) { |
| if (check_enrollment_value == "0") |
| return FRERequirement::kExplicitlyNotRequired; |
| if (check_enrollment_value == "1") |
| return FRERequirement::kExplicitlyRequired; |
| } |
| if (!provider->GetMachineStatistic(system::kActivateDateKey, nullptr) && |
| !provider->GetEnterpriseMachineID().empty()) { |
| return FRERequirement::kNotRequired; |
| } |
| return FRERequirement::kRequired; |
| } |
| |
| AutoEnrollmentController::AutoEnrollmentController() |
| : system_clock_sync_waiter_(std::make_unique<SystemClockSyncWaiter>()) {} |
| |
| AutoEnrollmentController::~AutoEnrollmentController() {} |
| |
| void AutoEnrollmentController::Start() { |
| switch (state_) { |
| case policy::AUTO_ENROLLMENT_STATE_PENDING: |
| // Abort re-start if the check is still running. |
| return; |
| case policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT: |
| case policy::AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT: |
| case policy::AUTO_ENROLLMENT_STATE_TRIGGER_ZERO_TOUCH: |
| // Abort re-start when there's already a final decision. |
| return; |
| |
| case policy::AUTO_ENROLLMENT_STATE_IDLE: |
| case policy::AUTO_ENROLLMENT_STATE_CONNECTION_ERROR: |
| case policy::AUTO_ENROLLMENT_STATE_SERVER_ERROR: |
| // Continue (re-)start. |
| break; |
| } |
| |
| // If a client is being created or already existing, bail out. |
| if (client_start_weak_factory_.HasWeakPtrs() || client_) { |
| LOG(ERROR) << "Auto-enrollment client is already running."; |
| return; |
| } |
| |
| // Arm the belts-and-suspenders timer to avoid hangs. |
| safeguard_timer_.Start(FROM_HERE, kSafeguardTimeout, |
| base::BindRepeating(&AutoEnrollmentController::Timeout, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| // The system clock sync state is not known yet, and this |
| // |AutoEnrollmentController| could wait for it if requested. |
| system_clock_sync_state_ = SystemClockSyncState::kCanWaitForSync; |
| StartWithSystemClockSyncState(); |
| } |
| |
| void AutoEnrollmentController::StartWithSystemClockSyncState() { |
| bool may_request_system_clock_sync = !system_clock_sync_wait_requested_; |
| |
| DetermineAutoEnrollmentCheckType(); |
| if (auto_enrollment_check_type_ == AutoEnrollmentCheckType::kNone) { |
| if (may_request_system_clock_sync && system_clock_sync_wait_requested_) { |
| // Set state before waiting for the system clock sync, because |
| // |WaitForSystemClockSync| may invoke its callback synchronously if the |
| // system clock sync status is already known. |
| UpdateState(policy::AUTO_ENROLLMENT_STATE_PENDING); |
| |
| // Use |client_start_weak_factory_| so the callback is not invoked if |
| // |Timeout| has been called in the meantime (after |kSafeguardTimeout|). |
| system_clock_sync_waiter_->WaitForSystemClockSync( |
| base::BindOnce(&AutoEnrollmentController::OnSystemClockSyncResult, |
| client_start_weak_factory_.GetWeakPtr())); |
| return; |
| } |
| UpdateState(policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT); |
| return; |
| } |
| |
| // Start by checking if the device has already been owned. |
| UpdateState(policy::AUTO_ENROLLMENT_STATE_PENDING); |
| DeviceSettingsService::Get()->GetOwnershipStatusAsync( |
| base::Bind(&AutoEnrollmentController::OnOwnershipStatusCheckDone, |
| client_start_weak_factory_.GetWeakPtr())); |
| } |
| |
| void AutoEnrollmentController::Retry() { |
| if (client_) |
| client_->Retry(); |
| else |
| Start(); |
| } |
| |
| std::unique_ptr<AutoEnrollmentController::ProgressCallbackList::Subscription> |
| AutoEnrollmentController::RegisterProgressCallback( |
| const ProgressCallbackList::CallbackType& callback) { |
| return progress_callbacks_.Add(callback); |
| } |
| |
| void AutoEnrollmentController::SetAutoEnrollmentClientFactoryForTesting( |
| policy::AutoEnrollmentClient::Factory* auto_enrollment_client_factory) { |
| testing_auto_enrollment_client_factory_ = auto_enrollment_client_factory; |
| } |
| |
| AutoEnrollmentController::InitialEnrollmentRequirement |
| AutoEnrollmentController::GetInitialEnrollmentRequirement() { |
| system::StatisticsProvider* provider = |
| system::StatisticsProvider::GetInstance(); |
| system::FactoryPingEmbargoState embargo_state = |
| system::GetFactoryPingEmbargoState(provider); |
| if (provider->GetEnterpriseMachineID().empty()) { |
| LOG(WARNING) |
| << "Skip Initial Enrollment Check due to missing serial number."; |
| RecordInitialEnrollmentRequirement( |
| InitialEnrollmentRequirementHistogramValue:: |
| kNotRequiredSerialNumberMissing, |
| system_clock_sync_state_); |
| return InitialEnrollmentRequirement::kNotRequired; |
| } |
| |
| std::string rlz_brand_code; |
| const bool rlz_brand_code_found = |
| provider->GetMachineStatistic(system::kRlzBrandCodeKey, &rlz_brand_code); |
| if (!rlz_brand_code_found || rlz_brand_code.empty()) { |
| LOG(WARNING) << "Skip Initial Enrollment Check due to missing brand code."; |
| RecordInitialEnrollmentRequirement( |
| InitialEnrollmentRequirementHistogramValue:: |
| kNotRequiredBrandCodeMissing, |
| system_clock_sync_state_); |
| return InitialEnrollmentRequirement::kNotRequired; |
| } |
| |
| if (system_clock_sync_state_ == SystemClockSyncState::kCanWaitForSync && |
| (embargo_state == system::FactoryPingEmbargoState::kInvalid || |
| embargo_state == system::FactoryPingEmbargoState::kNotPassed)) { |
| // Wait for the system clock to become synchronized and check again. |
| system_clock_sync_wait_requested_ = true; |
| return InitialEnrollmentRequirement::kNotRequired; |
| } |
| |
| const char* system_clock_log_info = |
| system_clock_sync_state_ == SystemClockSyncState::kSynchronized |
| ? "system clock in sync" |
| : "system clock sync failed"; |
| if (embargo_state == system::FactoryPingEmbargoState::kInvalid) { |
| LOG(WARNING) |
| << "Skip Initial Enrollment Check due to invalid embargo date (" |
| << system_clock_log_info << ")."; |
| RecordInitialEnrollmentRequirement( |
| InitialEnrollmentRequirementHistogramValue:: |
| kNotRequiredEmbargoEndDateInvalid, |
| system_clock_sync_state_); |
| return InitialEnrollmentRequirement::kNotRequired; |
| } |
| if (embargo_state == system::FactoryPingEmbargoState::kNotPassed) { |
| LOG(WARNING) << "Skip Initial Enrollment Check because the device is in " |
| "the embargo period (" |
| << system_clock_log_info << ")."; |
| RecordInitialEnrollmentRequirement( |
| InitialEnrollmentRequirementHistogramValue::kNotRequiredInEmbargoPeriod, |
| system_clock_sync_state_); |
| return InitialEnrollmentRequirement::kNotRequired; |
| } |
| |
| RecordInitialEnrollmentRequirement( |
| InitialEnrollmentRequirementHistogramValue::kRequired, |
| system_clock_sync_state_); |
| return InitialEnrollmentRequirement::kRequired; |
| } |
| |
| void AutoEnrollmentController::DetermineAutoEnrollmentCheckType() { |
| // Skip everything if neither FRE nor Initial Enrollment are enabled. |
| if (!IsEnabled()) { |
| VLOG(1) << "Auto-enrollment disabled"; |
| auto_enrollment_check_type_ = AutoEnrollmentCheckType::kNone; |
| return; |
| } |
| |
| // Skip everything if GAIA is disabled. |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(switches::kDisableGaiaServices)) { |
| VLOG(1) << "Auto-enrollment disabled: command line (gaia)."; |
| auto_enrollment_check_type_ = AutoEnrollmentCheckType::kNone; |
| return; |
| } |
| |
| // Skip everything if the device was in consumer mode previously. |
| fre_requirement_ = GetFRERequirement(); |
| VLOG(1) << FRERequirementToString(fre_requirement_); |
| if (fre_requirement_ == FRERequirement::kExplicitlyNotRequired) { |
| VLOG(1) << "Auto-enrollment disabled: VPD"; |
| auto_enrollment_check_type_ = AutoEnrollmentCheckType::kNone; |
| return; |
| } |
| |
| if (ShouldDoFRECheck(command_line, fre_requirement_)) { |
| // FRE has precedence over Initial Enrollment. |
| VLOG(1) << "Proceeding with FRE check"; |
| auto_enrollment_check_type_ = AutoEnrollmentCheckType::kFRE; |
| return; |
| } |
| |
| if (ShouldDoInitialEnrollmentCheck()) { |
| VLOG(1) << "Proceeding with Initial Enrollment check"; |
| auto_enrollment_check_type_ = AutoEnrollmentCheckType::kInitialEnrollment; |
| return; |
| } |
| |
| auto_enrollment_check_type_ = AutoEnrollmentCheckType::kNone; |
| } |
| |
| // static |
| bool AutoEnrollmentController::ShouldDoFRECheck( |
| base::CommandLine* command_line, |
| FRERequirement fre_requirement) { |
| // Skip FRE check if modulus configuration is not present. |
| if (!command_line->HasSwitch(switches::kEnterpriseEnrollmentInitialModulus) && |
| !command_line->HasSwitch(switches::kEnterpriseEnrollmentModulusLimit)) { |
| VLOG(1) << "FRE disabled: command line (config)"; |
| return false; |
| } |
| |
| // Skip FRE check if it is not enabled by command-line switches. |
| if (!IsFREEnabled()) { |
| VLOG(1) << "FRE disabled"; |
| return false; |
| } |
| |
| // Skip FRE check if it is not required according to the device state. |
| if (fre_requirement == FRERequirement::kNotRequired) |
| return false; |
| |
| return true; |
| } |
| |
| // static |
| bool AutoEnrollmentController::ShouldDoInitialEnrollmentCheck() { |
| // Skip Initial Enrollment check if it is not enabled according to |
| // command-line flags. |
| if (!IsInitialEnrollmentEnabled()) |
| return false; |
| |
| // Skip Initial Enrollment check if it is not required according to the |
| // device state. |
| if (GetInitialEnrollmentRequirement() == |
| InitialEnrollmentRequirement::kNotRequired) |
| return false; |
| |
| return true; |
| } |
| |
| void AutoEnrollmentController::OnOwnershipStatusCheckDone( |
| DeviceSettingsService::OwnershipStatus status) { |
| switch (status) { |
| case DeviceSettingsService::OWNERSHIP_NONE: |
| switch (auto_enrollment_check_type_) { |
| case AutoEnrollmentCheckType::kFRE: |
| // For FRE, request state keys first. |
| g_browser_process->platform_part() |
| ->browser_policy_connector_chromeos() |
| ->GetStateKeysBroker() |
| ->RequestStateKeys( |
| base::BindOnce(&AutoEnrollmentController::StartClientForFRE, |
| client_start_weak_factory_.GetWeakPtr())); |
| break; |
| case AutoEnrollmentCheckType::kInitialEnrollment: |
| StartClientForInitialEnrollment(); |
| break; |
| case AutoEnrollmentCheckType::kNone: |
| // The ownership check is only triggered if |
| // |auto_enrollment_check_type_| indicates that an auto-enrollment |
| // check should be done. |
| NOTREACHED(); |
| break; |
| } |
| return; |
| case DeviceSettingsService::OWNERSHIP_TAKEN: |
| VLOG(1) << "Device already owned, skipping auto-enrollment check."; |
| UpdateState(policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT); |
| return; |
| case DeviceSettingsService::OWNERSHIP_UNKNOWN: |
| LOG(ERROR) << "Ownership unknown, skipping auto-enrollment check."; |
| UpdateState(policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT); |
| return; |
| } |
| } |
| |
| void AutoEnrollmentController::StartClientForFRE( |
| const std::vector<std::string>& state_keys) { |
| if (state_keys.empty()) { |
| LOG(ERROR) << "No state keys available"; |
| if (fre_requirement_ == FRERequirement::kExplicitlyRequired) { |
| // Retry to fetch the state keys. For devices where FRE is required to be |
| // checked, we can't proceed with empty state keys. |
| g_browser_process->platform_part() |
| ->browser_policy_connector_chromeos() |
| ->GetStateKeysBroker() |
| ->RequestStateKeys( |
| base::BindOnce(&AutoEnrollmentController::StartClientForFRE, |
| client_start_weak_factory_.GetWeakPtr())); |
| } else { |
| UpdateState(policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT); |
| } |
| return; |
| } |
| |
| policy::DeviceManagementService* service = |
| InitializeAndGetDeviceManagementService(); |
| |
| int power_initial = |
| GetSanitizedArg(switches::kEnterpriseEnrollmentInitialModulus); |
| int power_limit = |
| GetSanitizedArg(switches::kEnterpriseEnrollmentModulusLimit); |
| if (power_initial > power_limit) { |
| LOG(ERROR) << "Initial auto-enrollment modulus is larger than the limit, " |
| "clamping to the limit."; |
| power_initial = power_limit; |
| } |
| |
| client_ = GetAutoEnrollmentClientFactory()->CreateForFRE( |
| base::BindRepeating(&AutoEnrollmentController::UpdateState, |
| weak_ptr_factory_.GetWeakPtr()), |
| service, g_browser_process->local_state(), |
| g_browser_process->system_network_context_manager() |
| ->GetSharedURLLoaderFactory(), |
| state_keys.front(), power_initial, power_limit); |
| |
| VLOG(1) << "Starting auto-enrollment client for FRE."; |
| client_->Start(); |
| } |
| |
| void AutoEnrollmentController::OnSystemClockSyncResult( |
| SystemClockSyncState system_clock_sync_state) { |
| system_clock_sync_state_ = system_clock_sync_state; |
| StartWithSystemClockSyncState(); |
| } |
| |
| void AutoEnrollmentController::StartClientForInitialEnrollment() { |
| policy::DeviceManagementService* service = |
| InitializeAndGetDeviceManagementService(); |
| |
| // Initial Enrollment does not transfer any data in the initial exchange, and |
| // supports uploading up to |kInitialEnrollmentModulusPowerLimit| bits of the |
| // identifier hash. |
| const int power_initial = 0; |
| const int power_limit = kInitialEnrollmentModulusPowerLimit; |
| |
| system::StatisticsProvider* provider = |
| system::StatisticsProvider::GetInstance(); |
| std::string serial_number = provider->GetEnterpriseMachineID(); |
| std::string rlz_brand_code; |
| const bool rlz_brand_code_found = |
| provider->GetMachineStatistic(system::kRlzBrandCodeKey, &rlz_brand_code); |
| // The initial enrollment check should not be started if the serial number or |
| // brand code are missing. This is ensured in |
| // |GetInitialEnrollmentRequirement|. |
| CHECK(!serial_number.empty() && rlz_brand_code_found && |
| !rlz_brand_code.empty()); |
| |
| client_ = GetAutoEnrollmentClientFactory()->CreateForInitialEnrollment( |
| base::BindRepeating(&AutoEnrollmentController::UpdateState, |
| weak_ptr_factory_.GetWeakPtr()), |
| service, g_browser_process->local_state(), |
| g_browser_process->system_network_context_manager() |
| ->GetSharedURLLoaderFactory(), |
| serial_number, rlz_brand_code, power_initial, power_limit, |
| kInitialEnrollmentModulusPowerOutdatedServer); |
| |
| VLOG(1) << "Starting auto-enrollment client for Initial Enrollment."; |
| client_->Start(); |
| } |
| |
| void AutoEnrollmentController::UpdateState( |
| policy::AutoEnrollmentState new_state) { |
| VLOG(1) << "New auto-enrollment state: " << new_state; |
| state_ = new_state; |
| |
| // Stop the safeguard timer once a result comes in. |
| switch (state_) { |
| case policy::AUTO_ENROLLMENT_STATE_IDLE: |
| case policy::AUTO_ENROLLMENT_STATE_PENDING: |
| break; |
| case policy::AUTO_ENROLLMENT_STATE_CONNECTION_ERROR: |
| case policy::AUTO_ENROLLMENT_STATE_SERVER_ERROR: |
| case policy::AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT: |
| case policy::AUTO_ENROLLMENT_STATE_TRIGGER_ZERO_TOUCH: |
| case policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT: |
| safeguard_timer_.Stop(); |
| break; |
| } |
| |
| if (state_ == policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT) { |
| // Cryptohome D-Bus service may not be available yet. See |
| // https://crbug.com/841627. |
| DBusThreadManager::Get() |
| ->GetCryptohomeClient() |
| ->WaitForServiceToBeAvailable(base::BindOnce( |
| &AutoEnrollmentController::StartRemoveFirmwareManagementParameters, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| progress_callbacks_.Notify(state_); |
| } |
| } |
| |
| void AutoEnrollmentController::StartRemoveFirmwareManagementParameters( |
| bool service_is_ready) { |
| DCHECK_EQ(policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT, state_); |
| if (!service_is_ready) { |
| LOG(ERROR) << "Failed waiting for cryptohome D-Bus service availability."; |
| progress_callbacks_.Notify(state_); |
| return; |
| } |
| |
| cryptohome::RemoveFirmwareManagementParametersRequest request; |
| DBusThreadManager::Get() |
| ->GetCryptohomeClient() |
| ->RemoveFirmwareManagementParametersFromTpm( |
| request, |
| base::BindOnce( |
| &AutoEnrollmentController::OnFirmwareManagementParametersRemoved, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void AutoEnrollmentController::OnFirmwareManagementParametersRemoved( |
| base::Optional<cryptohome::BaseReply> reply) { |
| if (!reply.has_value()) |
| LOG(ERROR) << "Failed to remove firmware management parameters."; |
| |
| progress_callbacks_.Notify(state_); |
| } |
| |
| void AutoEnrollmentController::Timeout() { |
| // When tightening the FRE flows, as a cautionary measure (to prevent |
| // interference with consumer devices) timeout was chosen to only enforce FRE |
| // for EXPLICTLY_REQUIRED. |
| // TODO(igorcov): Investigate the remaining causes of hitting timeout and |
| // potentially either remove the timeout altogether or enforce FRE in the |
| // REQUIRED case as well. |
| // TODO(mnissler): Add UMA to track results of auto-enrollment checks. |
| if (client_start_weak_factory_.HasWeakPtrs() && |
| fre_requirement_ != FRERequirement::kExplicitlyRequired) { |
| // If the callbacks to check ownership status or state keys are still |
| // pending, there's a bug in the code running on the device. No use in |
| // retrying anything, need to fix that bug. |
| LOG(ERROR) << "Failed to start auto-enrollment check, fix the code!"; |
| UpdateState(policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT); |
| } else { |
| // This can actually happen in some cases, for example when state key |
| // generation is waiting for time sync or the server just doesn't reply and |
| // keeps the connection open. |
| LOG(ERROR) << "AutoEnrollmentClient didn't complete within time limit."; |
| UpdateState(policy::AUTO_ENROLLMENT_STATE_CONNECTION_ERROR); |
| } |
| |
| // Reset state. |
| if (client_) { |
| // Cancelling the |client_| allows it to determine whether |
| // its protocol finished before login was complete. |
| client_.release()->CancelAndDeleteSoon(); |
| } |
| |
| // Make sure to nuke pending |client_| start sequences. |
| client_start_weak_factory_.InvalidateWeakPtrs(); |
| } |
| |
| policy::AutoEnrollmentClient::Factory* |
| AutoEnrollmentController::GetAutoEnrollmentClientFactory() { |
| static base::NoDestructor<policy::AutoEnrollmentClientImpl::FactoryImpl> |
| default_factory; |
| if (testing_auto_enrollment_client_factory_) |
| return testing_auto_enrollment_client_factory_; |
| |
| return default_factory.get(); |
| } |
| |
| } // namespace chromeos |