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