Home | History | Annotate | Download | only in policy
      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 
     26 namespace em = enterprise_management;
     27 
     28 namespace {
     29 
     30 // UMA histogram names.
     31 const char kUMAProtocolTime[] = "Enterprise.AutoEnrollmentProtocolTime";
     32 const char kUMAExtraTime[] = "Enterprise.AutoEnrollmentExtraTime";
     33 const char kUMARequestStatus[] = "Enterprise.AutoEnrollmentRequestStatus";
     34 const 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).
     40 const 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.
     44 int 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|.
     69 int 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 
     80 namespace policy {
     81 
     82 AutoEnrollmentClient::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   DCHECK(!completion_callback_.is_null());
     98   if (!serial_number.empty())
     99     serial_number_hash_ = crypto::SHA256HashString(serial_number);
    100   net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
    101 }
    102 
    103 AutoEnrollmentClient::~AutoEnrollmentClient() {
    104   net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
    105 }
    106 
    107 // static
    108 void AutoEnrollmentClient::RegisterPrefs(PrefRegistrySimple* registry) {
    109   registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false);
    110   registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1);
    111 }
    112 
    113 // static
    114 bool AutoEnrollmentClient::IsDisabled() {
    115   CommandLine* command_line = CommandLine::ForCurrentProcess();
    116   return !command_line->HasSwitch(
    117              chromeos::switches::kEnterpriseEnrollmentInitialModulus) &&
    118          !command_line->HasSwitch(
    119              chromeos::switches::kEnterpriseEnrollmentModulusLimit);
    120 }
    121 
    122 // static
    123 AutoEnrollmentClient* 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(
    138       chromeos::switches::kEnterpriseEnrollmentInitialModulus);
    139   int power_limit = GetSanitizedArg(
    140       chromeos::switches::kEnterpriseEnrollmentModulusLimit);
    141   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
    157 void AutoEnrollmentClient::CancelAutoEnrollment() {
    158   PrefService* local_state = g_browser_process->local_state();
    159   local_state->SetBoolean(prefs::kShouldAutoEnroll, false);
    160   local_state->CommitPendingWrite();
    161 }
    162 
    163 void 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_;
    172   } else if (device_management_service_) {
    173     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 
    188 void 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 
    201 void 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 
    220 bool 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 
    242 void AutoEnrollmentClient::SendRequest(int power) {
    243   if (power < 0 || power > power_limit_ || serial_number_hash_.empty()) {
    244     NOTREACHED();
    245     OnRequestDone();
    246     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 
    272 void AutoEnrollmentClient::OnRequestCompletion(
    273     DeviceManagementStatus status,
    274     int net_error,
    275     const em::DeviceManagementResponse& response) {
    276   if (status != DM_STATUS_SUCCESS || !response.has_auto_enrollment_response()) {
    277     LOG(ERROR) << "Auto enrollment error: " << status;
    278     UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, status);
    279     if (status == DM_STATUS_REQUEST_FAILED)
    280       UMA_HISTOGRAM_SPARSE_SLOWLY(kUMANetworkErrorCode, -net_error);
    281     // The client will retry if a network change is detected.
    282     OnRequestDone();
    283     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       }
    312       // Remember this value, so that eventual retries start with the correct
    313       // modulus.
    314       power_initial_ = power;
    315       SendRequest(power);
    316       return;
    317     }
    318   } else {
    319     // Server should have sent down a list of hashes to try.
    320     should_auto_enroll_ = IsSerialInProtobuf(enrollment_response.hash());
    321     // 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();
    326     LOG(INFO) << "Auto enrollment complete, should_auto_enroll = "
    327               << should_auto_enroll_;
    328   }
    329 
    330   // Auto-enrollment done.
    331   UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, DM_STATUS_SUCCESS);
    332   OnProtocolDone();
    333 }
    334 
    335 bool 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 
    344 void AutoEnrollmentClient::OnProtocolDone() {
    345   // 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_;
    355     UMA_HISTOGRAM_CUSTOM_TIMES(kUMAProtocolTime, delta, kMin, kMax, kBuckets);
    356   }
    357   base::TimeDelta delta = kZero;
    358   if (!time_extra_start_.is_null())
    359     delta = now - time_extra_start_;
    360   // 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.
    363   UMA_HISTOGRAM_CUSTOM_TIMES(kUMAExtraTime, delta, kMin, kMax, kBuckets);
    364 
    365   if (!completion_callback_.is_null())
    366     completion_callback_.Run();
    367 
    368   OnRequestDone();
    369 }
    370 
    371 void 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   }
    379 }
    380 
    381 }  // namespace policy
    382