auto_enrollment_client.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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/message_loop_proxy.h"
13#include "base/metrics/histogram.h"
14#include "base/metrics/sparse_histogram.h"
15#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"
22#include "chrome/common/pref_names.h"
23#include "chromeos/chromeos_switches.h"
24#include "crypto/sha2.h"
25
26namespace em = enterprise_management;
27
28namespace {
29
30// 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
37// 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_);
97  if (!serial_number.empty())
98    serial_number_hash_ = crypto::SHA256HashString(serial_number);
99}
100
101AutoEnrollmentClient::~AutoEnrollmentClient() {}
102
103// static
104void AutoEnrollmentClient::RegisterPrefs(PrefRegistrySimple* registry) {
105  registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false);
106  registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1);
107}
108
109// static
110bool AutoEnrollmentClient::IsDisabled() {
111  CommandLine* command_line = CommandLine::ForCurrentProcess();
112  return !command_line->HasSwitch(
113             chromeos::switches::kEnterpriseEnrollmentInitialModulus) &&
114         !command_line->HasSwitch(
115             chromeos::switches::kEnterpriseEnrollmentModulusLimit);
116}
117
118// static
119AutoEnrollmentClient* AutoEnrollmentClient::Create(
120    const base::Closure& completion_callback) {
121  // The client won't do anything if |service| is NULL.
122  DeviceManagementService* service = NULL;
123  if (IsDisabled()) {
124    VLOG(1) << "Auto-enrollment is disabled";
125  } else {
126    std::string url = BrowserPolicyConnector::GetDeviceManagementUrl();
127    if (!url.empty()) {
128      service = new DeviceManagementService(url);
129      service->ScheduleInitialization(0);
130    }
131  }
132
133  int power_initial = GetSanitizedArg(
134      chromeos::switches::kEnterpriseEnrollmentInitialModulus);
135  int power_limit = GetSanitizedArg(
136      chromeos::switches::kEnterpriseEnrollmentModulusLimit);
137  if (power_initial > power_limit) {
138    LOG(ERROR) << "Initial auto-enrollment modulus is larger than the limit, "
139               << "clamping to the limit.";
140    power_initial = power_limit;
141  }
142
143  return new AutoEnrollmentClient(
144      completion_callback,
145      service,
146      g_browser_process->local_state(),
147      DeviceCloudPolicyManagerChromeOS::GetMachineID(),
148      power_initial,
149      power_limit);
150}
151
152// static
153void AutoEnrollmentClient::CancelAutoEnrollment() {
154  PrefService* local_state = g_browser_process->local_state();
155  local_state->SetBoolean(prefs::kShouldAutoEnroll, false);
156  local_state->CommitPendingWrite();
157}
158
159void AutoEnrollmentClient::Start() {
160  // Drop the previous job and reset state.
161  request_job_.reset();
162  should_auto_enroll_ = false;
163  time_start_ = base::Time();  // reset to null.
164
165  if (GetCachedDecision()) {
166    VLOG(1) << "AutoEnrollmentClient: using cached decision: "
167            << should_auto_enroll_;
168  } else if (device_management_service_.get()) {
169    if (serial_number_hash_.empty()) {
170      LOG(ERROR) << "Failed to get the hash of the serial number, "
171                 << "will not attempt to auto-enroll.";
172    } else {
173      time_start_ = base::Time::Now();
174      SendRequest(power_initial_);
175      // Don't invoke the callback now.
176      return;
177    }
178  }
179
180  // Auto-enrollment can't even start, so we're done.
181  OnProtocolDone();
182}
183
184void AutoEnrollmentClient::CancelAndDeleteSoon() {
185  if (time_start_.is_null()) {
186    // The client isn't running, just delete it.
187    delete this;
188  } else {
189    // Client still running, but our owner isn't interested in the result
190    // anymore. Wait until the protocol completes to measure the extra time
191    // needed.
192    time_extra_start_ = base::Time::Now();
193    completion_callback_.Reset();
194  }
195}
196
197bool AutoEnrollmentClient::GetCachedDecision() {
198  const PrefService::Preference* should_enroll_pref =
199      local_state_->FindPreference(prefs::kShouldAutoEnroll);
200  const PrefService::Preference* previous_limit_pref =
201      local_state_->FindPreference(prefs::kAutoEnrollmentPowerLimit);
202  bool should_auto_enroll = false;
203  int previous_limit = -1;
204
205  if (!should_enroll_pref ||
206      should_enroll_pref->IsDefaultValue() ||
207      !should_enroll_pref->GetValue()->GetAsBoolean(&should_auto_enroll) ||
208      !previous_limit_pref ||
209      previous_limit_pref->IsDefaultValue() ||
210      !previous_limit_pref->GetValue()->GetAsInteger(&previous_limit) ||
211      power_limit_ > previous_limit) {
212    return false;
213  }
214
215  should_auto_enroll_ = should_auto_enroll;
216  return true;
217}
218
219void AutoEnrollmentClient::SendRequest(int power) {
220  if (power < 0 || power > power_limit_ || serial_number_hash_.empty()) {
221    NOTREACHED();
222    OnProtocolDone();
223    return;
224  }
225
226  requests_sent_++;
227
228  // Only power-of-2 moduli are supported for now. These are computed by taking
229  // the lower |power| bits of the hash.
230  uint64 remainder = 0;
231  for (int i = 0; 8 * i < power; ++i) {
232    uint64 byte = serial_number_hash_[31 - i] & 0xff;
233    remainder = remainder | (byte << (8 * i));
234  }
235  remainder = remainder & ((GG_UINT64_C(1) << power) - 1);
236
237  request_job_.reset(
238      device_management_service_->CreateJob(
239          DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT));
240  request_job_->SetClientID(device_id_);
241  em::DeviceAutoEnrollmentRequest* request =
242      request_job_->GetRequest()->mutable_auto_enrollment_request();
243  request->set_remainder(remainder);
244  request->set_modulus(GG_INT64_C(1) << power);
245  request_job_->Start(base::Bind(&AutoEnrollmentClient::OnRequestCompletion,
246                                 base::Unretained(this)));
247}
248
249void AutoEnrollmentClient::OnRequestCompletion(
250    DeviceManagementStatus status,
251    int net_error,
252    const em::DeviceManagementResponse& response) {
253  if (status != DM_STATUS_SUCCESS || !response.has_auto_enrollment_response()) {
254    LOG(ERROR) << "Auto enrollment error: " << status;
255    UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, status);
256    if (status == DM_STATUS_REQUEST_FAILED)
257      UMA_HISTOGRAM_SPARSE_SLOWLY(kUMANetworkErrorCode, -net_error);
258    OnProtocolDone();
259    return;
260  }
261
262  const em::DeviceAutoEnrollmentResponse& enrollment_response =
263      response.auto_enrollment_response();
264  if (enrollment_response.has_expected_modulus()) {
265    // Server is asking us to retry with a different modulus.
266    int64 modulus = enrollment_response.expected_modulus();
267    int power = NextPowerOf2(modulus);
268    if ((GG_INT64_C(1) << power) != modulus) {
269      LOG(WARNING) << "Auto enrollment: the server didn't ask for a power-of-2 "
270                   << "modulus. Using the closest power-of-2 instead "
271                   << "(" << modulus << " vs 2^" << power << ")";
272    }
273    if (requests_sent_ >= 2) {
274      LOG(ERROR) << "Auto enrollment error: already retried with an updated "
275                 << "modulus but the server asked for a new one again: "
276                 << power;
277    } else if (power > power_limit_) {
278      LOG(ERROR) << "Auto enrollment error: the server asked for a larger "
279                 << "modulus than the client accepts (" << power << " vs "
280                 << power_limit_ << ").";
281    } else {
282      // Retry at most once with the modulus that the server requested.
283      if (power <= power_initial_) {
284        LOG(WARNING) << "Auto enrollment: the server asked to use a modulus ("
285                     << power << ") that isn't larger than the first used ("
286                     << power_initial_ << "). Retrying anyway.";
287      }
288      SendRequest(power);
289      // Don't invoke the callback yet.
290      return;
291    }
292  } else {
293    // Server should have sent down a list of hashes to try.
294    should_auto_enroll_ = IsSerialInProtobuf(enrollment_response.hash());
295    // Cache the current decision in local_state, so that it is reused in case
296    // the device reboots before enrolling.
297    local_state_->SetBoolean(prefs::kShouldAutoEnroll, should_auto_enroll_);
298    local_state_->SetInteger(prefs::kAutoEnrollmentPowerLimit, power_limit_);
299    local_state_->CommitPendingWrite();
300    LOG(INFO) << "Auto enrollment complete, should_auto_enroll = "
301              << should_auto_enroll_;
302  }
303
304  // Auto-enrollment done.
305  UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, DM_STATUS_SUCCESS);
306  OnProtocolDone();
307}
308
309bool AutoEnrollmentClient::IsSerialInProtobuf(
310      const google::protobuf::RepeatedPtrField<std::string>& hashes) {
311  for (int i = 0; i < hashes.size(); ++i) {
312    if (hashes.Get(i) == serial_number_hash_)
313      return true;
314  }
315  return false;
316}
317
318void AutoEnrollmentClient::OnProtocolDone() {
319  // The mininum time can't be 0, must be at least 1.
320  static const base::TimeDelta kMin = base::TimeDelta::FromMilliseconds(1);
321  static const base::TimeDelta kMax = base::TimeDelta::FromMinutes(5);
322  // However, 0 can still be sampled.
323  static const base::TimeDelta kZero = base::TimeDelta::FromMilliseconds(0);
324  static const int kBuckets = 50;
325
326  base::Time now = base::Time::Now();
327  if (!time_start_.is_null()) {
328    base::TimeDelta delta = now - time_start_;
329    UMA_HISTOGRAM_CUSTOM_TIMES(kUMAProtocolTime, delta, kMin, kMax, kBuckets);
330    time_start_ = base::Time();
331  }
332  base::TimeDelta delta = kZero;
333  if (!time_extra_start_.is_null()) {
334    // CancelAndDeleteSoon() was invoked before.
335    delta = now - time_extra_start_;
336    base::MessageLoopProxy::current()->DeleteSoon(FROM_HERE, this);
337    time_extra_start_ = base::Time();
338  }
339  // This samples |kZero| when there was no need for extra time, so that we can
340  // measure the ratio of users that succeeded without needing a delay to the
341  // total users going through OOBE.
342  UMA_HISTOGRAM_CUSTOM_TIMES(kUMAExtraTime, delta, kMin, kMax, kBuckets);
343
344  if (!completion_callback_.is_null())
345    completion_callback_.Run();
346}
347
348}  // namespace policy
349