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