Home | History | Annotate | Download | only in attestation
      1 // Copyright (c) 2013 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/attestation/attestation_policy_observer.h"
      6 
      7 #include <string>
      8 
      9 #include "base/bind.h"
     10 #include "base/callback.h"
     11 #include "base/location.h"
     12 #include "base/time/time.h"
     13 #include "chrome/browser/chrome_notification_types.h"
     14 #include "chrome/browser/chromeos/attestation/attestation_ca_client.h"
     15 #include "chrome/browser/chromeos/attestation/attestation_key_payload.pb.h"
     16 #include "chrome/browser/chromeos/settings/cros_settings.h"
     17 #include "chrome/browser/policy/cloud/cloud_policy_client.h"
     18 #include "chrome/browser/policy/cloud/cloud_policy_manager.h"
     19 #include "chromeos/attestation/attestation_flow.h"
     20 #include "chromeos/cryptohome/async_method_caller.h"
     21 #include "chromeos/dbus/cryptohome_client.h"
     22 #include "chromeos/dbus/dbus_method_call_status.h"
     23 #include "chromeos/dbus/dbus_thread_manager.h"
     24 #include "content/public/browser/browser_thread.h"
     25 #include "content/public/browser/notification_details.h"
     26 #include "net/cert/x509_certificate.h"
     27 
     28 namespace {
     29 
     30 const char kEnterpriseMachineKey[] = "attest-ent-machine";
     31 
     32 // The number of days before a certificate expires during which it is
     33 // considered 'expiring soon' and replacement is initiated.  The Chrome OS CA
     34 // issues certificates with an expiry of at least two years.  This value has
     35 // been set large enough so that the majority of users will have gone through
     36 // a full sign-in during the period.
     37 const int kExpiryThresholdInDays = 30;
     38 const int kRetryDelay = 5;  // Seconds.
     39 const int kRetryLimit = 100;
     40 
     41 // A dbus callback which handles a boolean result.
     42 //
     43 // Parameters
     44 //   on_true - Called when status=success and value=true.
     45 //   on_false - Called when status=success and value=false.
     46 //   status - The dbus operation status.
     47 //   value - The value returned by the dbus operation.
     48 void DBusBoolRedirectCallback(const base::Closure& on_true,
     49                               const base::Closure& on_false,
     50                               const base::Closure& on_failure,
     51                               const tracked_objects::Location& from_here,
     52                               chromeos::DBusMethodCallStatus status,
     53                               bool value) {
     54   if (status != chromeos::DBUS_METHOD_CALL_SUCCESS) {
     55     LOG(ERROR) << "Cryptohome DBus method failed: " << from_here.ToString()
     56                << " - " << status;
     57     if (!on_failure.is_null())
     58       on_failure.Run();
     59     return;
     60   }
     61   const base::Closure& task = value ? on_true : on_false;
     62   if (!task.is_null())
     63     task.Run();
     64 }
     65 
     66 // A dbus callback which handles a string result.
     67 //
     68 // Parameters
     69 //   on_success - Called when status=success and result=true.
     70 //   status - The dbus operation status.
     71 //   result - The result returned by the dbus operation.
     72 //   data - The data returned by the dbus operation.
     73 void DBusStringCallback(
     74     const base::Callback<void(const std::string&)> on_success,
     75     const base::Closure& on_failure,
     76     const tracked_objects::Location& from_here,
     77     chromeos::DBusMethodCallStatus status,
     78     bool result,
     79     const std::string& data) {
     80   if (status != chromeos::DBUS_METHOD_CALL_SUCCESS || !result) {
     81     LOG(ERROR) << "Cryptohome DBus method failed: " << from_here.ToString()
     82                << " - " << status << " - " << result;
     83     if (!on_failure.is_null())
     84       on_failure.Run();
     85     return;
     86   }
     87   on_success.Run(data);
     88 }
     89 
     90 }  // namespace
     91 
     92 namespace chromeos {
     93 namespace attestation {
     94 
     95 AttestationPolicyObserver::AttestationPolicyObserver(
     96     policy::CloudPolicyClient* policy_client)
     97     : cros_settings_(CrosSettings::Get()),
     98       policy_client_(policy_client),
     99       cryptohome_client_(NULL),
    100       attestation_flow_(NULL),
    101       num_retries_(0),
    102       retry_delay_(kRetryDelay),
    103       weak_factory_(this) {
    104   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    105   cros_settings_->AddSettingsObserver(kDeviceAttestationEnabled, this);
    106   Start();
    107 }
    108 
    109 AttestationPolicyObserver::AttestationPolicyObserver(
    110     policy::CloudPolicyClient* policy_client,
    111     CryptohomeClient* cryptohome_client,
    112     AttestationFlow* attestation_flow)
    113     : cros_settings_(CrosSettings::Get()),
    114       policy_client_(policy_client),
    115       cryptohome_client_(cryptohome_client),
    116       attestation_flow_(attestation_flow),
    117       num_retries_(0),
    118       retry_delay_(kRetryDelay),
    119       weak_factory_(this) {
    120   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    121   cros_settings_->AddSettingsObserver(kDeviceAttestationEnabled, this);
    122   Start();
    123 }
    124 
    125 AttestationPolicyObserver::~AttestationPolicyObserver() {
    126   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    127   cros_settings_->RemoveSettingsObserver(kDeviceAttestationEnabled, this);
    128 }
    129 
    130 void AttestationPolicyObserver::Observe(
    131     int type,
    132     const content::NotificationSource& source,
    133     const content::NotificationDetails& details) {
    134   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    135   std::string* path = content::Details<std::string>(details).ptr();
    136   if (type != chrome::NOTIFICATION_SYSTEM_SETTING_CHANGED ||
    137       *path != kDeviceAttestationEnabled) {
    138     LOG(WARNING) << "AttestationPolicyObserver: Unexpected event received.";
    139     return;
    140   }
    141   num_retries_ = 0;
    142   Start();
    143 }
    144 
    145 void AttestationPolicyObserver::Start() {
    146   // If attestation is not enabled, there is nothing to do.
    147   bool enabled = false;
    148   if (!cros_settings_->GetBoolean(kDeviceAttestationEnabled, &enabled) ||
    149       !enabled)
    150     return;
    151 
    152   // We expect a registered CloudPolicyClient.
    153   if (!policy_client_->is_registered()) {
    154     LOG(ERROR) << "AttestationPolicyObserver: Invalid CloudPolicyClient.";
    155     return;
    156   }
    157 
    158   if (!cryptohome_client_)
    159     cryptohome_client_ = DBusThreadManager::Get()->GetCryptohomeClient();
    160 
    161   if (!attestation_flow_) {
    162     scoped_ptr<ServerProxy> attestation_ca_client(new AttestationCAClient());
    163     default_attestation_flow_.reset(new AttestationFlow(
    164         cryptohome::AsyncMethodCaller::GetInstance(),
    165         cryptohome_client_,
    166         attestation_ca_client.Pass()));
    167     attestation_flow_ = default_attestation_flow_.get();
    168   }
    169 
    170   // Start a dbus call to check if an Enterprise Machine Key already exists.
    171   base::Closure on_does_exist =
    172       base::Bind(&AttestationPolicyObserver::GetExistingCertificate,
    173                  weak_factory_.GetWeakPtr());
    174   base::Closure on_does_not_exist =
    175       base::Bind(&AttestationPolicyObserver::GetNewCertificate,
    176                  weak_factory_.GetWeakPtr());
    177   cryptohome_client_->TpmAttestationDoesKeyExist(
    178       KEY_DEVICE,
    179       kEnterpriseMachineKey,
    180       base::Bind(DBusBoolRedirectCallback,
    181                  on_does_exist,
    182                  on_does_not_exist,
    183                  base::Bind(&AttestationPolicyObserver::Reschedule,
    184                             weak_factory_.GetWeakPtr()),
    185                  FROM_HERE));
    186 }
    187 
    188 void AttestationPolicyObserver::GetNewCertificate() {
    189   // We can reuse the dbus callback handler logic.
    190   attestation_flow_->GetCertificate(
    191       PROFILE_ENTERPRISE_MACHINE_CERTIFICATE,
    192       true,  // Force a new key to be generated.
    193       base::Bind(DBusStringCallback,
    194                  base::Bind(&AttestationPolicyObserver::UploadCertificate,
    195                             weak_factory_.GetWeakPtr()),
    196                  base::Bind(&AttestationPolicyObserver::Reschedule,
    197                             weak_factory_.GetWeakPtr()),
    198                  FROM_HERE,
    199                  DBUS_METHOD_CALL_SUCCESS));
    200 }
    201 
    202 void AttestationPolicyObserver::GetExistingCertificate() {
    203   cryptohome_client_->TpmAttestationGetCertificate(
    204       KEY_DEVICE,
    205       kEnterpriseMachineKey,
    206       base::Bind(DBusStringCallback,
    207                  base::Bind(&AttestationPolicyObserver::CheckCertificateExpiry,
    208                             weak_factory_.GetWeakPtr()),
    209                  base::Bind(&AttestationPolicyObserver::Reschedule,
    210                             weak_factory_.GetWeakPtr()),
    211                  FROM_HERE));
    212 }
    213 
    214 void AttestationPolicyObserver::CheckCertificateExpiry(
    215     const std::string& certificate) {
    216   scoped_refptr<net::X509Certificate> x509(
    217       net::X509Certificate::CreateFromBytes(certificate.data(),
    218                                             certificate.length()));
    219   if (!x509.get() || x509->valid_expiry().is_null()) {
    220     LOG(WARNING) << "Failed to parse certificate, cannot check expiry.";
    221   } else {
    222     const base::TimeDelta threshold =
    223         base::TimeDelta::FromDays(kExpiryThresholdInDays);
    224     if ((base::Time::Now() + threshold) > x509->valid_expiry()) {
    225       // The certificate has expired or will soon, replace it.
    226       GetNewCertificate();
    227       return;
    228     }
    229   }
    230 
    231   // Get the payload and check if the certificate has already been uploaded.
    232   GetKeyPayload(base::Bind(&AttestationPolicyObserver::CheckIfUploaded,
    233                            weak_factory_.GetWeakPtr(),
    234                            certificate));
    235 }
    236 
    237 void AttestationPolicyObserver::UploadCertificate(
    238     const std::string& certificate) {
    239   policy_client_->UploadCertificate(
    240       certificate,
    241       base::Bind(&AttestationPolicyObserver::OnUploadComplete,
    242                  weak_factory_.GetWeakPtr()));
    243 }
    244 
    245 void AttestationPolicyObserver::CheckIfUploaded(
    246     const std::string& certificate,
    247     const std::string& key_payload) {
    248   AttestationKeyPayload payload_pb;
    249   if (!key_payload.empty() &&
    250       payload_pb.ParseFromString(key_payload) &&
    251       payload_pb.is_certificate_uploaded()) {
    252     // Already uploaded... nothing more to do.
    253     return;
    254   }
    255   UploadCertificate(certificate);
    256 }
    257 
    258 void AttestationPolicyObserver::GetKeyPayload(
    259     base::Callback<void(const std::string&)> callback) {
    260   cryptohome_client_->TpmAttestationGetKeyPayload(
    261       KEY_DEVICE,
    262       kEnterpriseMachineKey,
    263       base::Bind(DBusStringCallback,
    264                  callback,
    265                  base::Bind(&AttestationPolicyObserver::Reschedule,
    266                             weak_factory_.GetWeakPtr()),
    267                  FROM_HERE));
    268 }
    269 
    270 void AttestationPolicyObserver::OnUploadComplete(bool status) {
    271   if (!status)
    272     return;
    273   LOG(INFO) << "Enterprise Machine Certificate uploaded to DMServer.";
    274   GetKeyPayload(base::Bind(&AttestationPolicyObserver::MarkAsUploaded,
    275                            weak_factory_.GetWeakPtr()));
    276 }
    277 
    278 void AttestationPolicyObserver::MarkAsUploaded(const std::string& key_payload) {
    279   AttestationKeyPayload payload_pb;
    280   if (!key_payload.empty())
    281     payload_pb.ParseFromString(key_payload);
    282   payload_pb.set_is_certificate_uploaded(true);
    283   std::string new_payload;
    284   if (!payload_pb.SerializeToString(&new_payload)) {
    285     LOG(WARNING) << "Failed to serialize key payload.";
    286     return;
    287   }
    288   cryptohome_client_->TpmAttestationSetKeyPayload(
    289       KEY_DEVICE,
    290       kEnterpriseMachineKey,
    291       new_payload,
    292       base::Bind(DBusBoolRedirectCallback,
    293                  base::Closure(),
    294                  base::Closure(),
    295                  base::Closure(),
    296                  FROM_HERE));
    297 }
    298 
    299 void AttestationPolicyObserver::Reschedule() {
    300   if (++num_retries_ < kRetryLimit) {
    301     content::BrowserThread::PostDelayedTask(
    302         content::BrowserThread::UI, FROM_HERE,
    303         base::Bind(&AttestationPolicyObserver::Start,
    304                    weak_factory_.GetWeakPtr()),
    305         base::TimeDelta::FromSeconds(retry_delay_));
    306   } else {
    307     LOG(WARNING) << "AttestationPolicyObserver: Retry limit exceeded.";
    308   }
    309 }
    310 
    311 }  // namespace attestation
    312 }  // namespace chromeos
    313