blob: 197417dce785ec2c08a2e4f26aada86aa4f50b22 [file] [log] [blame]
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001// 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"
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +010012#include "base/message_loop/message_loop_proxy.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000013#include "base/metrics/histogram.h"
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +010014#include "base/metrics/sparse_histogram.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000015#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"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000022#include "chrome/common/pref_names.h"
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +010023#include "chromeos/chromeos_switches.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000024#include "crypto/sha2.h"
25
26namespace em = enterprise_management;
27
28namespace {
29
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +010030// UMA histogram names.
31const char kUMAProtocolTime[] = "Enterprise.AutoEnrollmentProtocolTime";
32const char kUMAExtraTime[] = "Enterprise.AutoEnrollmentExtraTime";
33const char kUMARequestStatus[] = "Enterprise.AutoEnrollmentRequestStatus";
34const char kUMANetworkErrorCode[] =
35 "Enterprise.AutoEnrollmentRequestNetworkErrorCode";
36
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000037// 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).
40const 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.
44int 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|.
69int 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
80namespace policy {
81
82AutoEnrollmentClient::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_);
Ben Murdochbb1529c2013-08-08 10:24:53 +010097 DCHECK(!completion_callback_.is_null());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000098 if (!serial_number.empty())
99 serial_number_hash_ = crypto::SHA256HashString(serial_number);
Ben Murdochbb1529c2013-08-08 10:24:53 +0100100 net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000101}
102
Ben Murdochbb1529c2013-08-08 10:24:53 +0100103AutoEnrollmentClient::~AutoEnrollmentClient() {
104 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
105}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000106
107// static
108void AutoEnrollmentClient::RegisterPrefs(PrefRegistrySimple* registry) {
109 registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false);
110 registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1);
111}
112
113// static
114bool AutoEnrollmentClient::IsDisabled() {
115 CommandLine* command_line = CommandLine::ForCurrentProcess();
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100116 return !command_line->HasSwitch(
117 chromeos::switches::kEnterpriseEnrollmentInitialModulus) &&
118 !command_line->HasSwitch(
119 chromeos::switches::kEnterpriseEnrollmentModulusLimit);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000120}
121
122// static
123AutoEnrollmentClient* 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(
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100138 chromeos::switches::kEnterpriseEnrollmentInitialModulus);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000139 int power_limit = GetSanitizedArg(
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100140 chromeos::switches::kEnterpriseEnrollmentModulusLimit);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000141 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
157void AutoEnrollmentClient::CancelAutoEnrollment() {
158 PrefService* local_state = g_browser_process->local_state();
159 local_state->SetBoolean(prefs::kShouldAutoEnroll, false);
160 local_state->CommitPendingWrite();
161}
162
163void 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_;
Ben Murdochbb1529c2013-08-08 10:24:53 +0100172 } else if (device_management_service_) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000173 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
188void 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
Ben Murdochbb1529c2013-08-08 10:24:53 +0100201void 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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000220bool 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
242void AutoEnrollmentClient::SendRequest(int power) {
243 if (power < 0 || power > power_limit_ || serial_number_hash_.empty()) {
244 NOTREACHED();
Ben Murdochbb1529c2013-08-08 10:24:53 +0100245 OnRequestDone();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000246 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
272void AutoEnrollmentClient::OnRequestCompletion(
273 DeviceManagementStatus status,
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100274 int net_error,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000275 const em::DeviceManagementResponse& response) {
276 if (status != DM_STATUS_SUCCESS || !response.has_auto_enrollment_response()) {
277 LOG(ERROR) << "Auto enrollment error: " << status;
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100278 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, status);
279 if (status == DM_STATUS_REQUEST_FAILED)
280 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMANetworkErrorCode, -net_error);
Ben Murdochbb1529c2013-08-08 10:24:53 +0100281 // The client will retry if a network change is detected.
282 OnRequestDone();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000283 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 }
Ben Murdochbb1529c2013-08-08 10:24:53 +0100312 // Remember this value, so that eventual retries start with the correct
313 // modulus.
314 power_initial_ = power;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000315 SendRequest(power);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000316 return;
317 }
318 } else {
319 // Server should have sent down a list of hashes to try.
320 should_auto_enroll_ = IsSerialInProtobuf(enrollment_response.hash());
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100321 // 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();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000326 LOG(INFO) << "Auto enrollment complete, should_auto_enroll = "
327 << should_auto_enroll_;
328 }
329
330 // Auto-enrollment done.
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100331 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, DM_STATUS_SUCCESS);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000332 OnProtocolDone();
333}
334
335bool 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
344void AutoEnrollmentClient::OnProtocolDone() {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000345 // 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_;
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100355 UMA_HISTOGRAM_CUSTOM_TIMES(kUMAProtocolTime, delta, kMin, kMax, kBuckets);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000356 }
357 base::TimeDelta delta = kZero;
Ben Murdochbb1529c2013-08-08 10:24:53 +0100358 if (!time_extra_start_.is_null())
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000359 delta = now - time_extra_start_;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000360 // 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.
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100363 UMA_HISTOGRAM_CUSTOM_TIMES(kUMAExtraTime, delta, kMin, kMax, kBuckets);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000364
365 if (!completion_callback_.is_null())
366 completion_callback_.Run();
Ben Murdochbb1529c2013-08-08 10:24:53 +0100367
368 OnRequestDone();
369}
370
371void 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 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000379}
380
381} // namespace policy