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