| // 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 "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 "chrome/browser/policy/cloud/cloud_policy_client.h" |
| #include "chrome/browser/policy/cloud/cloud_policy_manager.h" |
| #include "chromeos/attestation/attestation_flow.h" |
| #include "chromeos/cryptohome/async_method_caller.h" |
| #include "chromeos/dbus/cryptohome_client.h" |
| #include "chromeos/dbus/dbus_method_call_status.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_details.h" |
| #include "net/cert/x509_certificate.h" |
| |
| namespace { |
| |
| const char kEnterpriseMachineKey[] = "attest-ent-machine"; |
| |
| // 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(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| cros_settings_->AddSettingsObserver(kDeviceAttestationEnabled, 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(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| cros_settings_->AddSettingsObserver(kDeviceAttestationEnabled, this); |
| Start(); |
| } |
| |
| AttestationPolicyObserver::~AttestationPolicyObserver() { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| cros_settings_->RemoveSettingsObserver(kDeviceAttestationEnabled, this); |
| } |
| |
| void AttestationPolicyObserver::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| std::string* path = content::Details<std::string>(details).ptr(); |
| if (type != chrome::NOTIFICATION_SYSTEM_SETTING_CHANGED || |
| *path != kDeviceAttestationEnabled) { |
| LOG(WARNING) << "AttestationPolicyObserver: Unexpected event received."; |
| return; |
| } |
| 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_) { |
| scoped_ptr<ServerProxy> attestation_ca_client(new AttestationCAClient()); |
| default_attestation_flow_.reset(new AttestationFlow( |
| cryptohome::AsyncMethodCaller::GetInstance(), |
| cryptohome_client_, |
| attestation_ca_client.Pass())); |
| 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, |
| 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, |
| std::string(), // 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, |
| 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& certificate) { |
| scoped_refptr<net::X509Certificate> x509( |
| net::X509Certificate::CreateFromBytes(certificate.data(), |
| certificate.length())); |
| if (!x509.get() || x509->valid_expiry().is_null()) { |
| LOG(WARNING) << "Failed to parse certificate, cannot check expiry."; |
| } else { |
| 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; |
| } |
| } |
| |
| // Get the payload and check if the certificate has already been uploaded. |
| GetKeyPayload(base::Bind(&AttestationPolicyObserver::CheckIfUploaded, |
| weak_factory_.GetWeakPtr(), |
| certificate)); |
| } |
| |
| void AttestationPolicyObserver::UploadCertificate( |
| const std::string& certificate) { |
| policy_client_->UploadCertificate( |
| certificate, |
| base::Bind(&AttestationPolicyObserver::OnUploadComplete, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void AttestationPolicyObserver::CheckIfUploaded( |
| const std::string& certificate, |
| 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(certificate); |
| } |
| |
| void AttestationPolicyObserver::GetKeyPayload( |
| base::Callback<void(const std::string&)> callback) { |
| cryptohome_client_->TpmAttestationGetKeyPayload( |
| KEY_DEVICE, |
| kEnterpriseMachineKey, |
| base::Bind(DBusStringCallback, |
| callback, |
| base::Bind(&AttestationPolicyObserver::Reschedule, |
| weak_factory_.GetWeakPtr()), |
| FROM_HERE)); |
| } |
| |
| void AttestationPolicyObserver::OnUploadComplete(bool status) { |
| if (!status) |
| return; |
| LOG(INFO) << "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, |
| 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 |