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