| // 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/strings/string_number_conversions.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" |
| #include "chrome/browser/chromeos/policy/server_backed_state_keys_broker.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/system/statistics_provider.h" |
| #include "components/policy/core/common/cloud/device_management_service.h" |
| #include "net/url_request/url_request_context_getter.h" |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| // Maximum time to wait before forcing a decision. Note that download time for |
| // state key buckets can be non-negligible, especially on 2G connections. |
| const int kSafeguardTimeoutSeconds = 90; |
| |
| // 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) { |
| switch (requirement) { |
| case AutoEnrollmentController::REQUIRED: |
| return "Auto-enrollment required."; |
| case AutoEnrollmentController::NOT_REQUIRED: |
| return "Auto-enrollment disabled: first setup."; |
| case AutoEnrollmentController::EXPLICITLY_REQUIRED: |
| return "Auto-enrollment required: flag in VPD."; |
| case AutoEnrollmentController::EXPLICITLY_NOT_REQUIRED: |
| return "Auto-enrollment disabled: flag in VPD."; |
| } |
| |
| NOTREACHED(); |
| return std::string(); |
| } |
| |
| } // namespace |
| |
| const char AutoEnrollmentController::kForcedReEnrollmentAlways[] = "always"; |
| const char AutoEnrollmentController::kForcedReEnrollmentNever[] = "never"; |
| const char AutoEnrollmentController::kForcedReEnrollmentOfficialBuild[] = |
| "official"; |
| |
| // static |
| AutoEnrollmentController::Mode AutoEnrollmentController::GetMode() { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| |
| std::string command_line_mode = command_line->GetSwitchValueASCII( |
| switches::kEnterpriseEnableForcedReEnrollment); |
| if (command_line_mode == kForcedReEnrollmentAlways) { |
| return MODE_FORCED_RE_ENROLLMENT; |
| } else if (command_line_mode.empty() || |
| command_line_mode == kForcedReEnrollmentOfficialBuild) { |
| #if defined(OFFICIAL_BUILD) |
| std::string firmware_type; |
| const bool non_chrome_firmware = |
| system::StatisticsProvider::GetInstance()->GetMachineStatistic( |
| system::kFirmwareTypeKey, &firmware_type) && |
| firmware_type == system::kFirmwareTypeValueNonchrome; |
| return non_chrome_firmware ? MODE_NONE : MODE_FORCED_RE_ENROLLMENT; |
| #else |
| return MODE_NONE; |
| #endif |
| } else if (command_line_mode == kForcedReEnrollmentNever) { |
| return MODE_NONE; |
| } |
| |
| LOG(FATAL) << "Unknown auto-enrollment mode " << command_line_mode; |
| return MODE_NONE; |
| } |
| |
| // 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 AutoEnrollmentController::EXPLICITLY_NOT_REQUIRED; |
| if (check_enrollment_value == "1") |
| return AutoEnrollmentController::EXPLICITLY_REQUIRED; |
| } |
| if (!provider->GetMachineStatistic(system::kActivateDateKey, nullptr) && |
| !provider->GetEnterpriseMachineID().empty()) { |
| return AutoEnrollmentController::NOT_REQUIRED; |
| } |
| return AutoEnrollmentController::REQUIRED; |
| } |
| |
| AutoEnrollmentController::AutoEnrollmentController() {} |
| |
| 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; |
| } |
| |
| // Skip if GAIA is disabled or modulus configuration is not present. |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(chromeos::switches::kDisableGaiaServices) || |
| (!command_line->HasSwitch( |
| chromeos::switches::kEnterpriseEnrollmentInitialModulus) && |
| !command_line->HasSwitch( |
| chromeos::switches::kEnterpriseEnrollmentModulusLimit))) { |
| VLOG(1) << "Auto-enrollment disabled: command line."; |
| UpdateState(policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT); |
| return; |
| } |
| |
| // Skip if mode comes up as none. |
| if (GetMode() == MODE_NONE) { |
| VLOG(1) << "Auto-enrollment disabled: no mode."; |
| UpdateState(policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT); |
| return; |
| } |
| |
| fre_requirement_ = GetFRERequirement(); |
| VLOG(1) << FRERequirementToString(fre_requirement_); |
| if (fre_requirement_ == EXPLICITLY_NOT_REQUIRED || |
| fre_requirement_ == NOT_REQUIRED) { |
| UpdateState(policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT); |
| return; |
| } |
| |
| // 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, |
| base::TimeDelta::FromSeconds(kSafeguardTimeoutSeconds), |
| base::Bind(&AutoEnrollmentController::Timeout, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| // 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::OnOwnershipStatusCheckDone( |
| DeviceSettingsService::OwnershipStatus status) { |
| switch (status) { |
| case DeviceSettingsService::OWNERSHIP_NONE: |
| g_browser_process->platform_part() |
| ->browser_policy_connector_chromeos() |
| ->GetStateKeysBroker() |
| ->RequestStateKeys( |
| base::Bind(&AutoEnrollmentController::StartClient, |
| client_start_weak_factory_.GetWeakPtr())); |
| 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::StartClient( |
| const std::vector<std::string>& state_keys) { |
| if (state_keys.empty()) { |
| LOG(ERROR) << "No state keys available"; |
| if (fre_requirement_ == EXPLICITLY_REQUIRED) { |
| // 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::Bind(&AutoEnrollmentController::StartClient, |
| client_start_weak_factory_.GetWeakPtr())); |
| } else { |
| UpdateState(policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT); |
| } |
| return; |
| } |
| |
| policy::BrowserPolicyConnectorChromeOS* connector = |
| g_browser_process->platform_part()->browser_policy_connector_chromeos(); |
| policy::DeviceManagementService* service = |
| connector->device_management_service(); |
| service->ScheduleInitialization(0); |
| |
| int power_initial = |
| GetSanitizedArg(chromeos::switches::kEnterpriseEnrollmentInitialModulus); |
| int power_limit = |
| GetSanitizedArg(chromeos::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_ = policy::AutoEnrollmentClient::CreateForFRE( |
| base::Bind(&AutoEnrollmentController::UpdateState, |
| weak_ptr_factory_.GetWeakPtr()), |
| service, g_browser_process->local_state(), |
| g_browser_process->system_request_context(), state_keys.front(), |
| power_initial, power_limit); |
| |
| VLOG(1) << "Starting auto-enrollment client."; |
| 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) { |
| StartRemoveFirmwareManagementParameters(); |
| } else { |
| progress_callbacks_.Notify(state_); |
| } |
| } |
| |
| void AutoEnrollmentController::StartRemoveFirmwareManagementParameters() { |
| DCHECK_EQ(policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT, state_); |
| |
| cryptohome::RemoveFirmwareManagementParametersRequest request; |
| chromeos::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, error: " |
| << reply->error(); |
| } |
| |
| 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_ != EXPLICITLY_REQUIRED) { |
| // 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(); |
| } |
| |
| } // namespace chromeos |