| OLD | NEW |
| (Empty) |
| 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/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/policy/browser_policy_connector.h" | |
| 19 #include "chrome/browser/policy/device_cloud_policy_manager_chromeos.h" | |
| 20 #include "chrome/browser/policy/device_management_service.h" | |
| 21 #include "chrome/common/chrome_switches.h" | |
| 22 #include "chrome/common/pref_names.h" | |
| 23 #include "crypto/sha2.h" | |
| 24 | |
| 25 namespace em = enterprise_management; | |
| 26 | |
| 27 namespace { | |
| 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). | |
| 32 const 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. | |
| 36 int 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|. | |
| 61 int 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 | |
| 72 namespace policy { | |
| 73 | |
| 74 AutoEnrollmentClient::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 | |
| 93 AutoEnrollmentClient::~AutoEnrollmentClient() {} | |
| 94 | |
| 95 // static | |
| 96 void AutoEnrollmentClient::RegisterPrefs(PrefRegistrySimple* registry) { | |
| 97 registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false); | |
| 98 registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1); | |
| 99 } | |
| 100 | |
| 101 // static | |
| 102 bool 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 | |
| 110 AutoEnrollmentClient* 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 | |
| 144 void AutoEnrollmentClient::CancelAutoEnrollment() { | |
| 145 PrefService* local_state = g_browser_process->local_state(); | |
| 146 local_state->SetBoolean(prefs::kShouldAutoEnroll, false); | |
| 147 local_state->CommitPendingWrite(); | |
| 148 } | |
| 149 | |
| 150 void 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 | |
| 175 void 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 | |
| 188 bool 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 | |
| 210 void 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 | |
| 240 void 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 | |
| 290 bool 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 | |
| 299 void 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 | |
| OLD | NEW |