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