| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/attestation/enrollment_id_upload_manager.h" |
| |
| #include <optional> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/location.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/ash/attestation/attestation_ca_client.h" |
| #include "chrome/browser/ash/attestation/attestation_key_payload.pb.h" |
| #include "chromeos/ash/components/cryptohome/cryptohome_parameters.h" |
| #include "chromeos/ash/components/dbus/attestation/attestation_client.h" |
| #include "chromeos/ash/components/dbus/attestation/interface.pb.h" |
| #include "chromeos/ash/components/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/common/dbus_method_call_status.h" |
| #include "components/account_id/account_id.h" |
| #include "components/policy/core/common/cloud/cloud_policy_client.h" |
| #include "components/policy/core/common/cloud/cloud_policy_manager.h" |
| #include "components/user_manager/known_user.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "net/cert/x509_certificate.h" |
| |
| namespace { |
| |
| const int kRetryDelay = 5; // Seconds. |
| const int kRetryLimit = 100; |
| |
| } // namespace |
| |
| namespace ash { |
| namespace attestation { |
| |
| EnrollmentIdUploadManager::EnrollmentIdUploadManager( |
| policy::CloudPolicyClient* policy_client, |
| EnrollmentCertificateUploader* certificate_uploader) |
| : EnrollmentIdUploadManager(policy_client, |
| DeviceSettingsService::Get(), |
| certificate_uploader) {} |
| |
| EnrollmentIdUploadManager::EnrollmentIdUploadManager( |
| policy::CloudPolicyClient* policy_client, |
| DeviceSettingsService* device_settings_service, |
| EnrollmentCertificateUploader* certificate_uploader) |
| : device_settings_service_(device_settings_service), |
| policy_client_(policy_client), |
| certificate_uploader_(certificate_uploader), |
| num_retries_(0), |
| retry_limit_(kRetryLimit), |
| retry_delay_(kRetryDelay) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| device_settings_service_->AddObserver(this); |
| Start(); |
| } |
| |
| EnrollmentIdUploadManager::~EnrollmentIdUploadManager() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(device_settings_service_); |
| device_settings_service_->RemoveObserver(this); |
| } |
| |
| void EnrollmentIdUploadManager::DeviceSettingsUpdated() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| num_retries_ = 0; |
| Start(); |
| } |
| |
| void EnrollmentIdUploadManager::Start() { |
| // If identification for enrollment isn't needed, there is nothing to do. |
| const enterprise_management::PolicyData* policy_data = |
| device_settings_service_->policy_data(); |
| if (!policy_data || !policy_data->enrollment_id_needed()) |
| return; |
| |
| // Do not check the result of the upload request, because DMServer will |
| // inform us if the enrollment ID is needed the next time device policies |
| // are fetched. |
| ObtainAndUploadEnrollmentId(base::DoNothing()); |
| } |
| |
| void EnrollmentIdUploadManager::ObtainAndUploadEnrollmentId( |
| UploadManagerCallback callback) { |
| DCHECK(!callback.is_null()); |
| |
| bool start = upload_manager_callbacks_.empty(); |
| upload_manager_callbacks_.push(std::move(callback)); |
| |
| // Do not allow multiple concurrent requests. |
| if (start) { |
| certificate_uploader_->ObtainAndUploadCertificate(base::BindOnce( |
| &EnrollmentIdUploadManager::OnEnrollmentCertificateUploaded, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void EnrollmentIdUploadManager::OnEnrollmentCertificateUploaded( |
| EnrollmentCertificateUploader::Status status) { |
| switch (status) { |
| case EnrollmentCertificateUploader::Status::kSuccess: |
| // Enrollment certificate uploaded successfully. No need to compute EID. |
| RunCallbacks(/*status=*/true); |
| break; |
| case EnrollmentCertificateUploader::Status::kFailedToFetch: |
| LOG(WARNING) << "EnrollmentIdUploadManager: Failed to fetch certificate. " |
| "Trying to compute and upload enrollment ID."; |
| GetEnrollmentId(); |
| break; |
| case EnrollmentCertificateUploader::Status::kFailedToUpload: |
| // Enrollment certificate was fetched but not uploaded. It can be uploaded |
| // later so we will not proceed with computed EID. |
| RunCallbacks(/*status=*/false); |
| break; |
| case EnrollmentCertificateUploader::Status::kInvalidClient: |
| // Enrollment certificate was not uploaded due to invalid |
| // `CloudPolicyClient`. The certificate can be uploaded later when the |
| // client is working again. The manager is also not able to upload EID |
| // with invalid `CloudPolicyClient` so there is no reason to fall back to |
| // EID computation. |
| RunCallbacks(/*status=*/false); |
| break; |
| } |
| } |
| |
| void EnrollmentIdUploadManager::GetEnrollmentId() { |
| // If we already uploaded an empty identification, we are done, because |
| // we asked to compute and upload it only if the PCA refused to give |
| // us an enrollment certificate, an error that will happen again (the |
| // AIK certificate sent to request an enrollment certificate does not |
| // contain an EID). |
| if (did_upload_empty_eid_) { |
| RunCallbacks(/*status=*/false); |
| return; |
| } |
| |
| // We expect a registered CloudPolicyClient. |
| if (!policy_client_->is_registered()) { |
| LOG(ERROR) << "EnrollmentIdUploadManager: Invalid CloudPolicyClient."; |
| RunCallbacks(/*status=*/false); |
| return; |
| } |
| |
| ::attestation::GetEnrollmentIdRequest request; |
| request.set_ignore_cache(true); |
| AttestationClient::Get()->GetEnrollmentId( |
| request, base::BindOnce(&EnrollmentIdUploadManager::OnGetEnrollmentId, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void EnrollmentIdUploadManager::OnGetEnrollmentId( |
| const ::attestation::GetEnrollmentIdReply& reply) { |
| if (reply.status() != ::attestation::STATUS_SUCCESS) { |
| LOG(WARNING) << "Failed to get enrollment ID: " << reply.status(); |
| RescheduleGetEnrollmentId(); |
| return; |
| } |
| if (reply.enrollment_id().empty()) { |
| LOG(WARNING) << "EnrollmentIdUploadManager: The enrollment identifier " |
| "obtained is empty."; |
| } |
| policy_client_->UploadEnterpriseEnrollmentId( |
| reply.enrollment_id(), |
| base::BindOnce(&EnrollmentIdUploadManager::OnUploadComplete, |
| weak_factory_.GetWeakPtr(), reply.enrollment_id())); |
| } |
| |
| void EnrollmentIdUploadManager::RescheduleGetEnrollmentId() { |
| if (++num_retries_ < retry_limit_) { |
| content::GetUIThreadTaskRunner({})->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&EnrollmentIdUploadManager::GetEnrollmentId, |
| weak_factory_.GetWeakPtr()), |
| base::Seconds(retry_delay_)); |
| } else { |
| LOG(WARNING) << "EnrollmentIdUploadManager: Retry limit exceeded."; |
| RunCallbacks(/*status=*/false); |
| } |
| } |
| |
| void EnrollmentIdUploadManager::OnUploadComplete( |
| const std::string& enrollment_id, |
| policy::CloudPolicyClient::Result result) { |
| const std::string printable_enrollment_id = |
| base::ToLowerASCII(base::HexEncode(enrollment_id)); |
| |
| if (!result.IsSuccess()) { |
| LOG(ERROR) << "Failed to upload Enrollment Identifier \"" |
| << printable_enrollment_id << "\" to DMServer."; |
| RunCallbacks(/*status=*/false); |
| return; |
| } |
| |
| if (enrollment_id.empty()) |
| did_upload_empty_eid_ = true; |
| |
| VLOG(1) << "Enrollment Identifier \"" << printable_enrollment_id |
| << "\" uploaded to DMServer."; |
| RunCallbacks(/*status=*/true); |
| } |
| |
| void EnrollmentIdUploadManager::RunCallbacks(bool status) { |
| std::queue<UploadManagerCallback> callbacks; |
| callbacks.swap(upload_manager_callbacks_); |
| |
| while (!callbacks.empty()) { |
| std::move(callbacks.front()).Run(status); |
| callbacks.pop(); |
| } |
| } |
| |
| } // namespace attestation |
| } // namespace ash |