auto_enrollment_client.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/chromeos/policy/auto_enrollment_client.h" 6 7#include "base/bind.h" 8#include "base/command_line.h" 9#include "base/guid.h" 10#include "base/location.h" 11#include "base/logging.h" 12#include "base/message_loop/message_loop_proxy.h" 13#include "base/metrics/histogram.h" 14#include "base/metrics/sparse_histogram.h" 15#include "base/prefs/pref_registry_simple.h" 16#include "base/prefs/pref_service.h" 17#include "base/strings/string_number_conversions.h" 18#include "chrome/browser/browser_process.h" 19#include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h" 20#include "chrome/browser/policy/browser_policy_connector.h" 21#include "chrome/browser/policy/cloud/device_management_service.h" 22#include "chrome/common/pref_names.h" 23#include "chromeos/chromeos_switches.h" 24#include "content/public/browser/browser_thread.h" 25#include "crypto/sha2.h" 26#include "net/url_request/url_request_context_getter.h" 27 28using content::BrowserThread; 29 30namespace em = enterprise_management; 31 32namespace { 33 34// UMA histogram names. 35const char kUMAProtocolTime[] = "Enterprise.AutoEnrollmentProtocolTime"; 36const char kUMAExtraTime[] = "Enterprise.AutoEnrollmentExtraTime"; 37const char kUMARequestStatus[] = "Enterprise.AutoEnrollmentRequestStatus"; 38const char kUMANetworkErrorCode[] = 39 "Enterprise.AutoEnrollmentRequestNetworkErrorCode"; 40 41// The modulus value is sent in an int64 field in the protobuf, whose maximum 42// value is 2^63-1. So 2^64 and 2^63 can't be represented as moduli and the 43// max is 2^62 (when the moduli are restricted to powers-of-2). 44const int kMaximumPower = 62; 45 46// Returns the int value of the |switch_name| argument, clamped to the [0, 62] 47// interval. Returns 0 if the argument doesn't exist or isn't an int value. 48int GetSanitizedArg(const std::string& switch_name) { 49 CommandLine* command_line = CommandLine::ForCurrentProcess(); 50 if (!command_line->HasSwitch(switch_name)) 51 return 0; 52 std::string value = command_line->GetSwitchValueASCII(switch_name); 53 int int_value; 54 if (!base::StringToInt(value, &int_value)) { 55 LOG(ERROR) << "Switch \"" << switch_name << "\" is not a valid int. " 56 << "Defaulting to 0."; 57 return 0; 58 } 59 if (int_value < 0) { 60 LOG(ERROR) << "Switch \"" << switch_name << "\" can't be negative. " 61 << "Using 0"; 62 return 0; 63 } 64 if (int_value > kMaximumPower) { 65 LOG(ERROR) << "Switch \"" << switch_name << "\" can't be greater than " 66 << kMaximumPower << ". Using " << kMaximumPower; 67 return kMaximumPower; 68 } 69 return int_value; 70} 71 72// Returns the power of the next power-of-2 starting at |value|. 73int NextPowerOf2(int64 value) { 74 for (int i = 0; i <= kMaximumPower; ++i) { 75 if ((GG_INT64_C(1) << i) >= value) 76 return i; 77 } 78 // No other value can be represented in an int64. 79 return kMaximumPower + 1; 80} 81 82} // namespace 83 84namespace policy { 85 86AutoEnrollmentClient::AutoEnrollmentClient(const base::Closure& callback, 87 DeviceManagementService* service, 88 PrefService* local_state, 89 const std::string& serial_number, 90 int power_initial, 91 int power_limit) 92 : completion_callback_(callback), 93 should_auto_enroll_(false), 94 device_id_(base::GenerateGUID()), 95 power_initial_(power_initial), 96 power_limit_(power_limit), 97 requests_sent_(0), 98 device_management_service_(service), 99 local_state_(local_state) { 100 DCHECK_LE(power_initial_, power_limit_); 101 DCHECK(!completion_callback_.is_null()); 102 if (!serial_number.empty()) 103 serial_number_hash_ = crypto::SHA256HashString(serial_number); 104 net::NetworkChangeNotifier::AddNetworkChangeObserver(this); 105} 106 107AutoEnrollmentClient::~AutoEnrollmentClient() { 108 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this); 109} 110 111// static 112void AutoEnrollmentClient::RegisterPrefs(PrefRegistrySimple* registry) { 113 registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false); 114 registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1); 115} 116 117// static 118bool AutoEnrollmentClient::IsDisabled() { 119 CommandLine* command_line = CommandLine::ForCurrentProcess(); 120 return !command_line->HasSwitch( 121 chromeos::switches::kEnterpriseEnrollmentInitialModulus) && 122 !command_line->HasSwitch( 123 chromeos::switches::kEnterpriseEnrollmentModulusLimit); 124} 125 126// static 127AutoEnrollmentClient* AutoEnrollmentClient::Create( 128 const base::Closure& completion_callback) { 129 // The client won't do anything if |service| is NULL. 130 DeviceManagementService* service = NULL; 131 if (IsDisabled()) { 132 VLOG(1) << "Auto-enrollment is disabled"; 133 } else { 134 BrowserPolicyConnector* connector = 135 g_browser_process->browser_policy_connector(); 136 service = connector->device_management_service(); 137 service->ScheduleInitialization(0); 138 } 139 140 int power_initial = GetSanitizedArg( 141 chromeos::switches::kEnterpriseEnrollmentInitialModulus); 142 int power_limit = GetSanitizedArg( 143 chromeos::switches::kEnterpriseEnrollmentModulusLimit); 144 if (power_initial > power_limit) { 145 LOG(ERROR) << "Initial auto-enrollment modulus is larger than the limit, " 146 << "clamping to the limit."; 147 power_initial = power_limit; 148 } 149 150 return new AutoEnrollmentClient( 151 completion_callback, 152 service, 153 g_browser_process->local_state(), 154 DeviceCloudPolicyManagerChromeOS::GetMachineID(), 155 power_initial, 156 power_limit); 157} 158 159// static 160void AutoEnrollmentClient::CancelAutoEnrollment() { 161 PrefService* local_state = g_browser_process->local_state(); 162 local_state->SetBoolean(prefs::kShouldAutoEnroll, false); 163 local_state->CommitPendingWrite(); 164} 165 166void AutoEnrollmentClient::Start() { 167 // Drop the previous job and reset state. 168 request_job_.reset(); 169 should_auto_enroll_ = false; 170 time_start_ = base::Time(); // reset to null. 171 172 if (GetCachedDecision()) { 173 VLOG(1) << "AutoEnrollmentClient: using cached decision: " 174 << should_auto_enroll_; 175 } else if (device_management_service_) { 176 if (serial_number_hash_.empty()) { 177 LOG(ERROR) << "Failed to get the hash of the serial number, " 178 << "will not attempt to auto-enroll."; 179 } else { 180 time_start_ = base::Time::Now(); 181 SendRequest(power_initial_); 182 // Don't invoke the callback now. 183 return; 184 } 185 } 186 187 // Auto-enrollment can't even start, so we're done. 188 OnProtocolDone(); 189} 190 191void AutoEnrollmentClient::CancelAndDeleteSoon() { 192 if (time_start_.is_null()) { 193 // The client isn't running, just delete it. 194 delete this; 195 } else { 196 // Client still running, but our owner isn't interested in the result 197 // anymore. Wait until the protocol completes to measure the extra time 198 // needed. 199 time_extra_start_ = base::Time::Now(); 200 completion_callback_.Reset(); 201 } 202} 203 204void AutoEnrollmentClient::OnNetworkChanged( 205 net::NetworkChangeNotifier::ConnectionType type) { 206 if (GetCachedDecision()) { 207 // A previous request already obtained a definitive response from the 208 // server, so there is no point in retrying; it will get the same decision. 209 return; 210 } 211 212 if (type != net::NetworkChangeNotifier::CONNECTION_NONE && 213 !completion_callback_.is_null() && 214 !request_job_ && 215 device_management_service_ && 216 !serial_number_hash_.empty()) { 217 VLOG(1) << "Retrying auto enrollment check after network changed"; 218 time_start_ = base::Time::Now(); 219 SendRequest(power_initial_); 220 } 221} 222 223bool AutoEnrollmentClient::GetCachedDecision() { 224 const PrefService::Preference* should_enroll_pref = 225 local_state_->FindPreference(prefs::kShouldAutoEnroll); 226 const PrefService::Preference* previous_limit_pref = 227 local_state_->FindPreference(prefs::kAutoEnrollmentPowerLimit); 228 bool should_auto_enroll = false; 229 int previous_limit = -1; 230 231 if (!should_enroll_pref || 232 should_enroll_pref->IsDefaultValue() || 233 !should_enroll_pref->GetValue()->GetAsBoolean(&should_auto_enroll) || 234 !previous_limit_pref || 235 previous_limit_pref->IsDefaultValue() || 236 !previous_limit_pref->GetValue()->GetAsInteger(&previous_limit) || 237 power_limit_ > previous_limit) { 238 return false; 239 } 240 241 should_auto_enroll_ = should_auto_enroll; 242 return true; 243} 244 245void AutoEnrollmentClient::SendRequest(int power) { 246 if (power < 0 || power > power_limit_ || serial_number_hash_.empty()) { 247 NOTREACHED(); 248 OnRequestDone(); 249 return; 250 } 251 252 requests_sent_++; 253 254 // Only power-of-2 moduli are supported for now. These are computed by taking 255 // the lower |power| bits of the hash. 256 uint64 remainder = 0; 257 for (int i = 0; 8 * i < power; ++i) { 258 uint64 byte = serial_number_hash_[31 - i] & 0xff; 259 remainder = remainder | (byte << (8 * i)); 260 } 261 remainder = remainder & ((GG_UINT64_C(1) << power) - 1); 262 263 request_job_.reset( 264 device_management_service_->CreateJob( 265 DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT)); 266 request_job_->SetClientID(device_id_); 267 em::DeviceAutoEnrollmentRequest* request = 268 request_job_->GetRequest()->mutable_auto_enrollment_request(); 269 request->set_remainder(remainder); 270 request->set_modulus(GG_INT64_C(1) << power); 271 request_job_->Start(base::Bind(&AutoEnrollmentClient::OnRequestCompletion, 272 base::Unretained(this))); 273} 274 275void AutoEnrollmentClient::OnRequestCompletion( 276 DeviceManagementStatus status, 277 int net_error, 278 const em::DeviceManagementResponse& response) { 279 if (status != DM_STATUS_SUCCESS || !response.has_auto_enrollment_response()) { 280 LOG(ERROR) << "Auto enrollment error: " << status; 281 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, status); 282 if (status == DM_STATUS_REQUEST_FAILED) 283 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMANetworkErrorCode, -net_error); 284 // The client will retry if a network change is detected. 285 OnRequestDone(); 286 return; 287 } 288 289 const em::DeviceAutoEnrollmentResponse& enrollment_response = 290 response.auto_enrollment_response(); 291 if (enrollment_response.has_expected_modulus()) { 292 // Server is asking us to retry with a different modulus. 293 int64 modulus = enrollment_response.expected_modulus(); 294 int power = NextPowerOf2(modulus); 295 if ((GG_INT64_C(1) << power) != modulus) { 296 LOG(WARNING) << "Auto enrollment: the server didn't ask for a power-of-2 " 297 << "modulus. Using the closest power-of-2 instead " 298 << "(" << modulus << " vs 2^" << power << ")"; 299 } 300 if (requests_sent_ >= 2) { 301 LOG(ERROR) << "Auto enrollment error: already retried with an updated " 302 << "modulus but the server asked for a new one again: " 303 << power; 304 } else if (power > power_limit_) { 305 LOG(ERROR) << "Auto enrollment error: the server asked for a larger " 306 << "modulus than the client accepts (" << power << " vs " 307 << power_limit_ << ")."; 308 } else { 309 // Retry at most once with the modulus that the server requested. 310 if (power <= power_initial_) { 311 LOG(WARNING) << "Auto enrollment: the server asked to use a modulus (" 312 << power << ") that isn't larger than the first used (" 313 << power_initial_ << "). Retrying anyway."; 314 } 315 // Remember this value, so that eventual retries start with the correct 316 // modulus. 317 power_initial_ = power; 318 SendRequest(power); 319 return; 320 } 321 } else { 322 // Server should have sent down a list of hashes to try. 323 should_auto_enroll_ = IsSerialInProtobuf(enrollment_response.hash()); 324 // Cache the current decision in local_state, so that it is reused in case 325 // the device reboots before enrolling. 326 local_state_->SetBoolean(prefs::kShouldAutoEnroll, should_auto_enroll_); 327 local_state_->SetInteger(prefs::kAutoEnrollmentPowerLimit, power_limit_); 328 local_state_->CommitPendingWrite(); 329 LOG(INFO) << "Auto enrollment complete, should_auto_enroll = " 330 << should_auto_enroll_; 331 } 332 333 // Auto-enrollment done. 334 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, DM_STATUS_SUCCESS); 335 OnProtocolDone(); 336} 337 338bool AutoEnrollmentClient::IsSerialInProtobuf( 339 const google::protobuf::RepeatedPtrField<std::string>& hashes) { 340 for (int i = 0; i < hashes.size(); ++i) { 341 if (hashes.Get(i) == serial_number_hash_) 342 return true; 343 } 344 return false; 345} 346 347void AutoEnrollmentClient::OnProtocolDone() { 348 // The mininum time can't be 0, must be at least 1. 349 static const base::TimeDelta kMin = base::TimeDelta::FromMilliseconds(1); 350 static const base::TimeDelta kMax = base::TimeDelta::FromMinutes(5); 351 // However, 0 can still be sampled. 352 static const base::TimeDelta kZero = base::TimeDelta::FromMilliseconds(0); 353 static const int kBuckets = 50; 354 355 base::Time now = base::Time::Now(); 356 if (!time_start_.is_null()) { 357 base::TimeDelta delta = now - time_start_; 358 UMA_HISTOGRAM_CUSTOM_TIMES(kUMAProtocolTime, delta, kMin, kMax, kBuckets); 359 } 360 base::TimeDelta delta = kZero; 361 if (!time_extra_start_.is_null()) 362 delta = now - time_extra_start_; 363 // This samples |kZero| when there was no need for extra time, so that we can 364 // measure the ratio of users that succeeded without needing a delay to the 365 // total users going through OOBE. 366 UMA_HISTOGRAM_CUSTOM_TIMES(kUMAExtraTime, delta, kMin, kMax, kBuckets); 367 368 if (!completion_callback_.is_null()) 369 completion_callback_.Run(); 370 371 OnRequestDone(); 372} 373 374void AutoEnrollmentClient::OnRequestDone() { 375 request_job_.reset(); 376 time_start_ = base::Time(); 377 378 if (completion_callback_.is_null()) { 379 // CancelAndDeleteSoon() was invoked before. 380 base::MessageLoopProxy::current()->DeleteSoon(FROM_HERE, this); 381 } 382} 383 384} // namespace policy 385