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/guid.h" 9 #include "base/location.h" 10 #include "base/logging.h" 11 #include "base/message_loop/message_loop_proxy.h" 12 #include "base/metrics/histogram.h" 13 #include "base/metrics/sparse_histogram.h" 14 #include "base/prefs/pref_registry_simple.h" 15 #include "base/prefs/pref_service.h" 16 #include "base/prefs/scoped_user_pref_update.h" 17 #include "chrome/browser/browser_process.h" 18 #include "chrome/browser/chromeos/policy/server_backed_device_state.h" 19 #include "chrome/common/chrome_content_client.h" 20 #include "chrome/common/pref_names.h" 21 #include "components/policy/core/common/cloud/device_management_service.h" 22 #include "components/policy/core/common/cloud/system_policy_request_context.h" 23 #include "content/public/browser/browser_thread.h" 24 #include "crypto/sha2.h" 25 #include "net/url_request/url_request_context_getter.h" 26 #include "policy/proto/device_management_backend.pb.h" 27 #include "url/gurl.h" 28 29 using content::BrowserThread; 30 31 namespace em = enterprise_management; 32 33 namespace policy { 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 // Returns the power of the next power-of-2 starting at |value|. 45 int NextPowerOf2(int64 value) { 46 for (int i = 0; i <= AutoEnrollmentClient::kMaximumPower; ++i) { 47 if ((GG_INT64_C(1) << i) >= value) 48 return i; 49 } 50 // No other value can be represented in an int64. 51 return AutoEnrollmentClient::kMaximumPower + 1; 52 } 53 54 // Sets or clears a value in a dictionary. 55 void UpdateDict(base::DictionaryValue* dict, 56 const char* pref_path, 57 bool set_or_clear, 58 base::Value* value) { 59 scoped_ptr<base::Value> scoped_value(value); 60 if (set_or_clear) 61 dict->Set(pref_path, scoped_value.release()); 62 else 63 dict->Remove(pref_path, NULL); 64 } 65 66 // Converts a restore mode enum value from the DM protocol into the 67 // corresponding prefs string constant. 68 std::string ConvertRestoreMode( 69 em::DeviceStateRetrievalResponse::RestoreMode restore_mode) { 70 switch (restore_mode) { 71 case em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE: 72 return std::string(); 73 case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_REQUESTED: 74 return kDeviceStateRestoreModeReEnrollmentRequested; 75 case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED: 76 return kDeviceStateRestoreModeReEnrollmentEnforced; 77 } 78 79 NOTREACHED() << "Bad restore mode " << restore_mode; 80 return std::string(); 81 } 82 83 } // namespace 84 85 AutoEnrollmentClient::AutoEnrollmentClient( 86 const ProgressCallback& callback, 87 DeviceManagementService* service, 88 PrefService* local_state, 89 scoped_refptr<net::URLRequestContextGetter> system_request_context, 90 const std::string& server_backed_state_key, 91 bool retrieve_device_state, 92 int power_initial, 93 int power_limit) 94 : progress_callback_(callback), 95 state_(AUTO_ENROLLMENT_STATE_IDLE), 96 has_server_state_(false), 97 device_state_available_(false), 98 device_id_(base::GenerateGUID()), 99 server_backed_state_key_(server_backed_state_key), 100 retrieve_device_state_(retrieve_device_state), 101 current_power_(power_initial), 102 power_limit_(power_limit), 103 modulus_updates_received_(0), 104 device_management_service_(service), 105 local_state_(local_state) { 106 request_context_ = new SystemPolicyRequestContext( 107 system_request_context, GetUserAgent()); 108 109 DCHECK_LE(current_power_, power_limit_); 110 DCHECK(!progress_callback_.is_null()); 111 if (!server_backed_state_key_.empty()) { 112 server_backed_state_key_hash_ = 113 crypto::SHA256HashString(server_backed_state_key_); 114 } 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 void AutoEnrollmentClient::CancelAutoEnrollment() { 129 PrefService* local_state = g_browser_process->local_state(); 130 local_state->SetBoolean(prefs::kShouldAutoEnroll, false); 131 local_state->ClearPref(prefs::kServerBackedDeviceState); 132 local_state->CommitPendingWrite(); 133 } 134 135 void AutoEnrollmentClient::Start() { 136 // (Re-)register the network change observer. 137 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this); 138 net::NetworkChangeNotifier::AddNetworkChangeObserver(this); 139 140 // Drop the previous job and reset state. 141 request_job_.reset(); 142 state_ = AUTO_ENROLLMENT_STATE_PENDING; 143 time_start_ = base::Time::Now(); 144 modulus_updates_received_ = 0; 145 has_server_state_ = false; 146 device_state_available_ = false; 147 148 NextStep(); 149 } 150 151 void AutoEnrollmentClient::Retry() { 152 RetryStep(); 153 } 154 155 void AutoEnrollmentClient::CancelAndDeleteSoon() { 156 if (time_start_.is_null() || !request_job_) { 157 // The client isn't running, just delete it. 158 delete this; 159 } else { 160 // Client still running, but our owner isn't interested in the result 161 // anymore. Wait until the protocol completes to measure the extra time 162 // needed. 163 time_extra_start_ = base::Time::Now(); 164 progress_callback_.Reset(); 165 } 166 } 167 168 void AutoEnrollmentClient::OnNetworkChanged( 169 net::NetworkChangeNotifier::ConnectionType type) { 170 if (type != net::NetworkChangeNotifier::CONNECTION_NONE && 171 !progress_callback_.is_null()) { 172 RetryStep(); 173 } 174 } 175 176 bool AutoEnrollmentClient::GetCachedDecision() { 177 const PrefService::Preference* has_server_state_pref = 178 local_state_->FindPreference(prefs::kShouldAutoEnroll); 179 const PrefService::Preference* previous_limit_pref = 180 local_state_->FindPreference(prefs::kAutoEnrollmentPowerLimit); 181 bool has_server_state = false; 182 int previous_limit = -1; 183 184 if (!has_server_state_pref || 185 has_server_state_pref->IsDefaultValue() || 186 !has_server_state_pref->GetValue()->GetAsBoolean(&has_server_state) || 187 !previous_limit_pref || 188 previous_limit_pref->IsDefaultValue() || 189 !previous_limit_pref->GetValue()->GetAsInteger(&previous_limit) || 190 power_limit_ > previous_limit) { 191 return false; 192 } 193 194 has_server_state_ = has_server_state; 195 return true; 196 } 197 198 bool AutoEnrollmentClient::RetryStep() { 199 // If there is a pending request job, let it finish. 200 if (request_job_) 201 return true; 202 203 if (GetCachedDecision()) { 204 // The bucket download check has completed already. If it came back 205 // positive, then device state should be (re-)downloaded. 206 if (has_server_state_) { 207 if (retrieve_device_state_ && !device_state_available_ && 208 SendDeviceStateRequest()) { 209 return true; 210 } 211 } 212 } else { 213 // Start bucket download. 214 if (SendBucketDownloadRequest()) 215 return true; 216 } 217 218 return false; 219 } 220 221 void AutoEnrollmentClient::ReportProgress(AutoEnrollmentState state) { 222 state_ = state; 223 if (progress_callback_.is_null()) { 224 base::MessageLoopProxy::current()->DeleteSoon(FROM_HERE, this); 225 } else { 226 progress_callback_.Run(state_); 227 } 228 } 229 230 void AutoEnrollmentClient::NextStep() { 231 if (!RetryStep()) { 232 // Protocol finished successfully, report result. 233 bool trigger_enrollment = false; 234 if (retrieve_device_state_) { 235 const base::DictionaryValue* device_state_dict = 236 local_state_->GetDictionary(prefs::kServerBackedDeviceState); 237 std::string restore_mode; 238 device_state_dict->GetString(kDeviceStateRestoreMode, &restore_mode); 239 trigger_enrollment = 240 (restore_mode == kDeviceStateRestoreModeReEnrollmentRequested || 241 restore_mode == kDeviceStateRestoreModeReEnrollmentEnforced); 242 } else { 243 trigger_enrollment = has_server_state_; 244 } 245 246 ReportProgress(trigger_enrollment ? AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT 247 : AUTO_ENROLLMENT_STATE_NO_ENROLLMENT); 248 } 249 } 250 251 bool AutoEnrollmentClient::SendBucketDownloadRequest() { 252 if (server_backed_state_key_hash_.empty()) 253 return false; 254 255 // Only power-of-2 moduli are supported for now. These are computed by taking 256 // the lower |current_power_| bits of the hash. 257 uint64 remainder = 0; 258 for (int i = 0; 8 * i < current_power_; ++i) { 259 uint64 byte = server_backed_state_key_hash_[31 - i] & 0xff; 260 remainder = remainder | (byte << (8 * i)); 261 } 262 remainder = remainder & ((GG_UINT64_C(1) << current_power_) - 1); 263 264 ReportProgress(AUTO_ENROLLMENT_STATE_PENDING); 265 266 request_job_.reset( 267 device_management_service_->CreateJob( 268 DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT, 269 request_context_.get())); 270 request_job_->SetClientID(device_id_); 271 em::DeviceAutoEnrollmentRequest* request = 272 request_job_->GetRequest()->mutable_auto_enrollment_request(); 273 request->set_remainder(remainder); 274 request->set_modulus(GG_INT64_C(1) << current_power_); 275 request_job_->Start( 276 base::Bind(&AutoEnrollmentClient::HandleRequestCompletion, 277 base::Unretained(this), 278 &AutoEnrollmentClient::OnBucketDownloadRequestCompletion)); 279 return true; 280 } 281 282 bool AutoEnrollmentClient::SendDeviceStateRequest() { 283 ReportProgress(AUTO_ENROLLMENT_STATE_PENDING); 284 285 request_job_.reset( 286 device_management_service_->CreateJob( 287 DeviceManagementRequestJob::TYPE_DEVICE_STATE_RETRIEVAL, 288 request_context_.get())); 289 request_job_->SetClientID(device_id_); 290 em::DeviceStateRetrievalRequest* request = 291 request_job_->GetRequest()->mutable_device_state_retrieval_request(); 292 request->set_server_backed_state_key(server_backed_state_key_); 293 request_job_->Start( 294 base::Bind(&AutoEnrollmentClient::HandleRequestCompletion, 295 base::Unretained(this), 296 &AutoEnrollmentClient::OnDeviceStateRequestCompletion)); 297 return true; 298 } 299 300 void AutoEnrollmentClient::HandleRequestCompletion( 301 RequestCompletionHandler handler, 302 DeviceManagementStatus status, 303 int net_error, 304 const em::DeviceManagementResponse& response) { 305 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, status); 306 if (status != DM_STATUS_SUCCESS) { 307 LOG(ERROR) << "Auto enrollment error: " << status; 308 if (status == DM_STATUS_REQUEST_FAILED) 309 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMANetworkErrorCode, -net_error); 310 request_job_.reset(); 311 312 // Abort if CancelAndDeleteSoon has been called meanwhile. 313 if (progress_callback_.is_null()) { 314 base::MessageLoopProxy::current()->DeleteSoon(FROM_HERE, this); 315 } else { 316 ReportProgress(status == DM_STATUS_REQUEST_FAILED 317 ? AUTO_ENROLLMENT_STATE_CONNECTION_ERROR 318 : AUTO_ENROLLMENT_STATE_SERVER_ERROR); 319 } 320 return; 321 } 322 323 bool progress = (this->*handler)(status, net_error, response); 324 request_job_.reset(); 325 if (progress) 326 NextStep(); 327 else 328 ReportProgress(AUTO_ENROLLMENT_STATE_SERVER_ERROR); 329 } 330 331 bool AutoEnrollmentClient::OnBucketDownloadRequestCompletion( 332 DeviceManagementStatus status, 333 int net_error, 334 const em::DeviceManagementResponse& response) { 335 bool progress = false; 336 const em::DeviceAutoEnrollmentResponse& enrollment_response = 337 response.auto_enrollment_response(); 338 if (!response.has_auto_enrollment_response()) { 339 LOG(ERROR) << "Server failed to provide auto-enrollment response."; 340 } else if (enrollment_response.has_expected_modulus()) { 341 // Server is asking us to retry with a different modulus. 342 modulus_updates_received_++; 343 344 int64 modulus = enrollment_response.expected_modulus(); 345 int power = NextPowerOf2(modulus); 346 if ((GG_INT64_C(1) << power) != modulus) { 347 LOG(WARNING) << "Auto enrollment: the server didn't ask for a power-of-2 " 348 << "modulus. Using the closest power-of-2 instead " 349 << "(" << modulus << " vs 2^" << power << ")"; 350 } 351 if (modulus_updates_received_ >= 2) { 352 LOG(ERROR) << "Auto enrollment error: already retried with an updated " 353 << "modulus but the server asked for a new one again: " 354 << power; 355 } else if (power > power_limit_) { 356 LOG(ERROR) << "Auto enrollment error: the server asked for a larger " 357 << "modulus than the client accepts (" << power << " vs " 358 << power_limit_ << ")."; 359 } else { 360 // Retry at most once with the modulus that the server requested. 361 if (power <= current_power_) { 362 LOG(WARNING) << "Auto enrollment: the server asked to use a modulus (" 363 << power << ") that isn't larger than the first used (" 364 << current_power_ << "). Retrying anyway."; 365 } 366 // Remember this value, so that eventual retries start with the correct 367 // modulus. 368 current_power_ = power; 369 return true; 370 } 371 } else { 372 // Server should have sent down a list of hashes to try. 373 has_server_state_ = IsIdHashInProtobuf(enrollment_response.hash()); 374 // Cache the current decision in local_state, so that it is reused in case 375 // the device reboots before enrolling. 376 local_state_->SetBoolean(prefs::kShouldAutoEnroll, has_server_state_); 377 local_state_->SetInteger(prefs::kAutoEnrollmentPowerLimit, power_limit_); 378 local_state_->CommitPendingWrite(); 379 VLOG(1) << "Auto enrollment check complete, has_server_state_ = " 380 << has_server_state_; 381 progress = true; 382 } 383 384 // Bucket download done, update UMA. 385 UpdateBucketDownloadTimingHistograms(); 386 return progress; 387 } 388 389 bool AutoEnrollmentClient::OnDeviceStateRequestCompletion( 390 DeviceManagementStatus status, 391 int net_error, 392 const enterprise_management::DeviceManagementResponse& response) { 393 bool progress = false; 394 if (!response.has_device_state_retrieval_response()) { 395 LOG(ERROR) << "Server failed to provide auto-enrollment response."; 396 } else { 397 const em::DeviceStateRetrievalResponse& state_response = 398 response.device_state_retrieval_response(); 399 { 400 DictionaryPrefUpdate dict(local_state_, prefs::kServerBackedDeviceState); 401 UpdateDict(dict.Get(), 402 kDeviceStateManagementDomain, 403 state_response.has_management_domain(), 404 new base::StringValue(state_response.management_domain())); 405 406 std::string restore_mode = 407 ConvertRestoreMode(state_response.restore_mode()); 408 UpdateDict(dict.Get(), 409 kDeviceStateRestoreMode, 410 !restore_mode.empty(), 411 new base::StringValue(restore_mode)); 412 } 413 local_state_->CommitPendingWrite(); 414 device_state_available_ = true; 415 progress = true; 416 } 417 418 return progress; 419 } 420 421 bool AutoEnrollmentClient::IsIdHashInProtobuf( 422 const google::protobuf::RepeatedPtrField<std::string>& hashes) { 423 for (int i = 0; i < hashes.size(); ++i) { 424 if (hashes.Get(i) == server_backed_state_key_hash_) 425 return true; 426 } 427 return false; 428 } 429 430 void AutoEnrollmentClient::UpdateBucketDownloadTimingHistograms() { 431 // The mininum time can't be 0, must be at least 1. 432 static const base::TimeDelta kMin = base::TimeDelta::FromMilliseconds(1); 433 static const base::TimeDelta kMax = base::TimeDelta::FromMinutes(5); 434 // However, 0 can still be sampled. 435 static const base::TimeDelta kZero = base::TimeDelta::FromMilliseconds(0); 436 static const int kBuckets = 50; 437 438 base::Time now = base::Time::Now(); 439 if (!time_start_.is_null()) { 440 base::TimeDelta delta = now - time_start_; 441 UMA_HISTOGRAM_CUSTOM_TIMES(kUMAProtocolTime, delta, kMin, kMax, kBuckets); 442 } 443 base::TimeDelta delta = kZero; 444 if (!time_extra_start_.is_null()) 445 delta = now - time_extra_start_; 446 // This samples |kZero| when there was no need for extra time, so that we can 447 // measure the ratio of users that succeeded without needing a delay to the 448 // total users going through OOBE. 449 UMA_HISTOGRAM_CUSTOM_TIMES(kUMAExtraTime, delta, kMin, kMax, kBuckets); 450 } 451 452 } // namespace policy 453