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