| // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "chrome/browser/chromeos/attestation/attestation_policy_observer.h" | 
 |  | 
 | #include <string> | 
 | #include <utility> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/callback.h" | 
 | #include "base/location.h" | 
 | #include "base/time/time.h" | 
 | #include "chrome/browser/chrome_notification_types.h" | 
 | #include "chrome/browser/chromeos/attestation/attestation_ca_client.h" | 
 | #include "chrome/browser/chromeos/attestation/attestation_key_payload.pb.h" | 
 | #include "chrome/browser/chromeos/settings/cros_settings.h" | 
 | #include "chromeos/attestation/attestation_flow.h" | 
 | #include "chromeos/cryptohome/async_method_caller.h" | 
 | #include "chromeos/cryptohome/cryptohome_parameters.h" | 
 | #include "chromeos/dbus/cryptohome_client.h" | 
 | #include "chromeos/dbus/dbus_method_call_status.h" | 
 | #include "chromeos/dbus/dbus_thread_manager.h" | 
 | #include "components/policy/core/common/cloud/cloud_policy_client.h" | 
 | #include "components/policy/core/common/cloud/cloud_policy_manager.h" | 
 | #include "components/signin/core/account_id/account_id.h" | 
 | #include "components/user_manager/known_user.h" | 
 | #include "content/public/browser/browser_thread.h" | 
 | #include "content/public/browser/notification_details.h" | 
 | #include "net/cert/pem_tokenizer.h" | 
 | #include "net/cert/x509_certificate.h" | 
 |  | 
 | namespace { | 
 |  | 
 | // The number of days before a certificate expires during which it is | 
 | // considered 'expiring soon' and replacement is initiated.  The Chrome OS CA | 
 | // issues certificates with an expiry of at least two years.  This value has | 
 | // been set large enough so that the majority of users will have gone through | 
 | // a full sign-in during the period. | 
 | const int kExpiryThresholdInDays = 30; | 
 | const int kRetryDelay = 5;  // Seconds. | 
 | const int kRetryLimit = 100; | 
 |  | 
 | // A dbus callback which handles a boolean result. | 
 | // | 
 | // Parameters | 
 | //   on_true - Called when status=success and value=true. | 
 | //   on_false - Called when status=success and value=false. | 
 | //   status - The dbus operation status. | 
 | //   value - The value returned by the dbus operation. | 
 | void DBusBoolRedirectCallback(const base::Closure& on_true, | 
 |                               const base::Closure& on_false, | 
 |                               const base::Closure& on_failure, | 
 |                               const tracked_objects::Location& from_here, | 
 |                               chromeos::DBusMethodCallStatus status, | 
 |                               bool value) { | 
 |   if (status != chromeos::DBUS_METHOD_CALL_SUCCESS) { | 
 |     LOG(ERROR) << "Cryptohome DBus method failed: " << from_here.ToString() | 
 |                << " - " << status; | 
 |     if (!on_failure.is_null()) | 
 |       on_failure.Run(); | 
 |     return; | 
 |   } | 
 |   const base::Closure& task = value ? on_true : on_false; | 
 |   if (!task.is_null()) | 
 |     task.Run(); | 
 | } | 
 |  | 
 | // A dbus callback which handles a string result. | 
 | // | 
 | // Parameters | 
 | //   on_success - Called when status=success and result=true. | 
 | //   status - The dbus operation status. | 
 | //   result - The result returned by the dbus operation. | 
 | //   data - The data returned by the dbus operation. | 
 | void DBusStringCallback( | 
 |     const base::Callback<void(const std::string&)> on_success, | 
 |     const base::Closure& on_failure, | 
 |     const tracked_objects::Location& from_here, | 
 |     chromeos::DBusMethodCallStatus status, | 
 |     bool result, | 
 |     const std::string& data) { | 
 |   if (status != chromeos::DBUS_METHOD_CALL_SUCCESS || !result) { | 
 |     LOG(ERROR) << "Cryptohome DBus method failed: " << from_here.ToString() | 
 |                << " - " << status << " - " << result; | 
 |     if (!on_failure.is_null()) | 
 |       on_failure.Run(); | 
 |     return; | 
 |   } | 
 |   on_success.Run(data); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | namespace chromeos { | 
 | namespace attestation { | 
 |  | 
 | AttestationPolicyObserver::AttestationPolicyObserver( | 
 |     policy::CloudPolicyClient* policy_client) | 
 |     : cros_settings_(CrosSettings::Get()), | 
 |       policy_client_(policy_client), | 
 |       cryptohome_client_(NULL), | 
 |       attestation_flow_(NULL), | 
 |       num_retries_(0), | 
 |       retry_delay_(kRetryDelay), | 
 |       weak_factory_(this) { | 
 |   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |   attestation_subscription_ = cros_settings_->AddSettingsObserver( | 
 |       kDeviceAttestationEnabled, | 
 |       base::Bind(&AttestationPolicyObserver::AttestationSettingChanged, | 
 |                  base::Unretained(this))); | 
 |   Start(); | 
 | } | 
 |  | 
 | AttestationPolicyObserver::AttestationPolicyObserver( | 
 |     policy::CloudPolicyClient* policy_client, | 
 |     CryptohomeClient* cryptohome_client, | 
 |     AttestationFlow* attestation_flow) | 
 |     : cros_settings_(CrosSettings::Get()), | 
 |       policy_client_(policy_client), | 
 |       cryptohome_client_(cryptohome_client), | 
 |       attestation_flow_(attestation_flow), | 
 |       num_retries_(0), | 
 |       retry_delay_(kRetryDelay), | 
 |       weak_factory_(this) { | 
 |   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |   attestation_subscription_ = cros_settings_->AddSettingsObserver( | 
 |       kDeviceAttestationEnabled, | 
 |       base::Bind(&AttestationPolicyObserver::AttestationSettingChanged, | 
 |                  base::Unretained(this))); | 
 |   Start(); | 
 | } | 
 |  | 
 | AttestationPolicyObserver::~AttestationPolicyObserver() { | 
 |   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 | } | 
 |  | 
 | void AttestationPolicyObserver::AttestationSettingChanged() { | 
 |   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |   num_retries_ = 0; | 
 |   Start(); | 
 | } | 
 |  | 
 | void AttestationPolicyObserver::Start() { | 
 |   // If attestation is not enabled, there is nothing to do. | 
 |   bool enabled = false; | 
 |   if (!cros_settings_->GetBoolean(kDeviceAttestationEnabled, &enabled) || | 
 |       !enabled) | 
 |     return; | 
 |  | 
 |   // We expect a registered CloudPolicyClient. | 
 |   if (!policy_client_->is_registered()) { | 
 |     LOG(ERROR) << "AttestationPolicyObserver: Invalid CloudPolicyClient."; | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!cryptohome_client_) | 
 |     cryptohome_client_ = DBusThreadManager::Get()->GetCryptohomeClient(); | 
 |  | 
 |   if (!attestation_flow_) { | 
 |     std::unique_ptr<ServerProxy> attestation_ca_client( | 
 |         new AttestationCAClient()); | 
 |     default_attestation_flow_.reset(new AttestationFlow( | 
 |         cryptohome::AsyncMethodCaller::GetInstance(), cryptohome_client_, | 
 |         std::move(attestation_ca_client))); | 
 |     attestation_flow_ = default_attestation_flow_.get(); | 
 |   } | 
 |  | 
 |   // Start a dbus call to check if an Enterprise Machine Key already exists. | 
 |   base::Closure on_does_exist = | 
 |       base::Bind(&AttestationPolicyObserver::GetExistingCertificate, | 
 |                  weak_factory_.GetWeakPtr()); | 
 |   base::Closure on_does_not_exist = | 
 |       base::Bind(&AttestationPolicyObserver::GetNewCertificate, | 
 |                  weak_factory_.GetWeakPtr()); | 
 |   cryptohome_client_->TpmAttestationDoesKeyExist( | 
 |       KEY_DEVICE, | 
 |       cryptohome::Identification(),  // Not used. | 
 |       kEnterpriseMachineKey, | 
 |       base::Bind(DBusBoolRedirectCallback, on_does_exist, on_does_not_exist, | 
 |                  base::Bind(&AttestationPolicyObserver::Reschedule, | 
 |                             weak_factory_.GetWeakPtr()), | 
 |                  FROM_HERE)); | 
 | } | 
 |  | 
 | void AttestationPolicyObserver::GetNewCertificate() { | 
 |   // We can reuse the dbus callback handler logic. | 
 |   attestation_flow_->GetCertificate( | 
 |       PROFILE_ENTERPRISE_MACHINE_CERTIFICATE, | 
 |       EmptyAccountId(),  // Not used. | 
 |       std::string(),     // Not used. | 
 |       true,              // Force a new key to be generated. | 
 |       base::Bind(DBusStringCallback, | 
 |                  base::Bind(&AttestationPolicyObserver::UploadCertificate, | 
 |                             weak_factory_.GetWeakPtr()), | 
 |                  base::Bind(&AttestationPolicyObserver::Reschedule, | 
 |                             weak_factory_.GetWeakPtr()), | 
 |                  FROM_HERE, DBUS_METHOD_CALL_SUCCESS)); | 
 | } | 
 |  | 
 | void AttestationPolicyObserver::GetExistingCertificate() { | 
 |   cryptohome_client_->TpmAttestationGetCertificate( | 
 |       KEY_DEVICE, | 
 |       cryptohome::Identification(),  // Not used. | 
 |       kEnterpriseMachineKey, | 
 |       base::Bind(DBusStringCallback, | 
 |                  base::Bind(&AttestationPolicyObserver::CheckCertificateExpiry, | 
 |                             weak_factory_.GetWeakPtr()), | 
 |                  base::Bind(&AttestationPolicyObserver::Reschedule, | 
 |                             weak_factory_.GetWeakPtr()), | 
 |                  FROM_HERE)); | 
 | } | 
 |  | 
 | void AttestationPolicyObserver::CheckCertificateExpiry( | 
 |     const std::string& pem_certificate_chain) { | 
 |   int num_certificates = 0; | 
 |   net::PEMTokenizer pem_tokenizer(pem_certificate_chain, {"CERTIFICATE"}); | 
 |   while (pem_tokenizer.GetNext()) { | 
 |     ++num_certificates; | 
 |     scoped_refptr<net::X509Certificate> x509 = | 
 |         net::X509Certificate::CreateFromBytes(pem_tokenizer.data().data(), | 
 |                                               pem_tokenizer.data().length()); | 
 |     if (!x509.get() || x509->valid_expiry().is_null()) { | 
 |       // This logic intentionally fails open. In theory this should not happen | 
 |       // but in practice parsing X.509 can be brittle and there are a lot of | 
 |       // factors including which underlying module is parsing the certificate, | 
 |       // whether that module performs more checks than just ASN.1/DER format, | 
 |       // and the server module that generated the certificate(s). Renewal is | 
 |       // expensive so we only renew certificates with good evidence that they | 
 |       // have expired or will soon expire; if we don't know, we don't renew. | 
 |       LOG(WARNING) << "Failed to parse certificate, cannot check expiry."; | 
 |       continue; | 
 |     } | 
 |     const base::TimeDelta threshold = | 
 |         base::TimeDelta::FromDays(kExpiryThresholdInDays); | 
 |     if ((base::Time::Now() + threshold) > x509->valid_expiry()) { | 
 |       // The certificate has expired or will soon, replace it. | 
 |       GetNewCertificate(); | 
 |       return; | 
 |     } | 
 |   } | 
 |   if (num_certificates == 0) { | 
 |     LOG(WARNING) << "Failed to parse certificate chain, cannot check expiry."; | 
 |   } | 
 |   // Get the payload and check if the certificate has already been uploaded. | 
 |   GetKeyPayload(base::Bind(&AttestationPolicyObserver::CheckIfUploaded, | 
 |                            weak_factory_.GetWeakPtr(), | 
 |                            pem_certificate_chain)); | 
 | } | 
 |  | 
 | void AttestationPolicyObserver::UploadCertificate( | 
 |     const std::string& pem_certificate_chain) { | 
 |   policy_client_->UploadCertificate( | 
 |       pem_certificate_chain, | 
 |       base::Bind(&AttestationPolicyObserver::OnUploadComplete, | 
 |                  weak_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void AttestationPolicyObserver::CheckIfUploaded( | 
 |     const std::string& pem_certificate_chain, | 
 |     const std::string& key_payload) { | 
 |   AttestationKeyPayload payload_pb; | 
 |   if (!key_payload.empty() && | 
 |       payload_pb.ParseFromString(key_payload) && | 
 |       payload_pb.is_certificate_uploaded()) { | 
 |     // Already uploaded... nothing more to do. | 
 |     return; | 
 |   } | 
 |   UploadCertificate(pem_certificate_chain); | 
 | } | 
 |  | 
 | void AttestationPolicyObserver::GetKeyPayload( | 
 |     base::Callback<void(const std::string&)> callback) { | 
 |   cryptohome_client_->TpmAttestationGetKeyPayload( | 
 |       KEY_DEVICE, | 
 |       cryptohome::Identification(),  // Not used. | 
 |       kEnterpriseMachineKey, | 
 |       base::Bind(DBusStringCallback, callback, | 
 |                  base::Bind(&AttestationPolicyObserver::Reschedule, | 
 |                             weak_factory_.GetWeakPtr()), | 
 |                  FROM_HERE)); | 
 | } | 
 |  | 
 | void AttestationPolicyObserver::OnUploadComplete(bool status) { | 
 |   if (!status) | 
 |     return; | 
 |   VLOG(1) << "Enterprise Machine Certificate uploaded to DMServer."; | 
 |   GetKeyPayload(base::Bind(&AttestationPolicyObserver::MarkAsUploaded, | 
 |                            weak_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void AttestationPolicyObserver::MarkAsUploaded(const std::string& key_payload) { | 
 |   AttestationKeyPayload payload_pb; | 
 |   if (!key_payload.empty()) | 
 |     payload_pb.ParseFromString(key_payload); | 
 |   payload_pb.set_is_certificate_uploaded(true); | 
 |   std::string new_payload; | 
 |   if (!payload_pb.SerializeToString(&new_payload)) { | 
 |     LOG(WARNING) << "Failed to serialize key payload."; | 
 |     return; | 
 |   } | 
 |   cryptohome_client_->TpmAttestationSetKeyPayload( | 
 |       KEY_DEVICE, | 
 |       cryptohome::Identification(),  // Not used. | 
 |       kEnterpriseMachineKey, new_payload, | 
 |       base::Bind(DBusBoolRedirectCallback, base::Closure(), base::Closure(), | 
 |                  base::Closure(), FROM_HERE)); | 
 | } | 
 |  | 
 | void AttestationPolicyObserver::Reschedule() { | 
 |   if (++num_retries_ < kRetryLimit) { | 
 |     content::BrowserThread::PostDelayedTask( | 
 |         content::BrowserThread::UI, FROM_HERE, | 
 |         base::Bind(&AttestationPolicyObserver::Start, | 
 |                    weak_factory_.GetWeakPtr()), | 
 |         base::TimeDelta::FromSeconds(retry_delay_)); | 
 |   } else { | 
 |     LOG(WARNING) << "AttestationPolicyObserver: Retry limit exceeded."; | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace attestation | 
 | }  // namespace chromeos |