| // Copyright 2019 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_certificate_uploader_impl.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/ash/attestation/attestation_ca_client.h" |
| #include "chrome/browser/ash/attestation/certificate_util.h" |
| #include "chromeos/ash/components/attestation/attestation_flow.h" |
| #include "chromeos/ash/components/attestation/attestation_flow_adaptive.h" |
| #include "chromeos/ash/components/cryptohome/cryptohome_parameters.h" |
| #include "chromeos/ash/components/dbus/constants/attestation_constants.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 "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace ash::attestation { |
| |
| namespace { |
| |
| // Constants for retrying certificate obtention and upload. |
| constexpr base::TimeDelta kRetryDelay = base::Seconds(5); |
| const int kRetryLimit = 100; |
| |
| void DBusPrivacyCACallback( |
| const base::RepeatingCallback<void(const std::string&)> on_success, |
| const base::RepeatingCallback<void(AttestationStatus)> on_failure, |
| const base::Location& from_here, |
| AttestationStatus status, |
| const std::string& data) { |
| DCHECK(on_success); |
| DCHECK(on_failure); |
| |
| if (status == ATTESTATION_SUCCESS) { |
| on_success.Run(data); |
| return; |
| } |
| |
| LOG(ERROR) << "Attestation DBus method or server called failed with status: " |
| << status << ": " << from_here.ToString(); |
| on_failure.Run(status); |
| } |
| |
| } // namespace |
| |
| EnrollmentCertificateUploaderImpl::EnrollmentCertificateUploaderImpl( |
| policy::CloudPolicyClient* policy_client) |
| : policy_client_(policy_client), |
| retry_limit_(kRetryLimit), |
| retry_delay_(kRetryDelay) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| } |
| |
| EnrollmentCertificateUploaderImpl::~EnrollmentCertificateUploaderImpl() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| } |
| |
| void EnrollmentCertificateUploaderImpl::ObtainAndUploadCertificate( |
| UploadCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| bool start = callbacks_.empty(); |
| callbacks_.push(std::move(callback)); |
| if (start) { |
| num_retries_ = 0; |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&EnrollmentCertificateUploaderImpl::Start, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void EnrollmentCertificateUploaderImpl::Start() { |
| if (!attestation_flow_) { |
| std::unique_ptr<ServerProxy> attestation_ca_client( |
| new AttestationCAClient()); |
| default_attestation_flow_ = std::make_unique<AttestationFlowAdaptive>( |
| std::move(attestation_ca_client)); |
| attestation_flow_ = default_attestation_flow_.get(); |
| } |
| |
| GetCertificate(/*force_new_key=*/false); |
| } |
| |
| void EnrollmentCertificateUploaderImpl::GetCertificate(bool force_new_key) { |
| if (!policy_client_->is_registered()) { |
| LOG(ERROR) << "CloudPolicyClient not registered."; |
| RunCallbacks(Status::kInvalidClient); |
| return; |
| } |
| |
| VLOG_IF(1, force_new_key) << "Fetching certificate with new key"; |
| attestation_flow_->GetCertificate( |
| /*certificate_profile=*/PROFILE_ENTERPRISE_ENROLLMENT_CERTIFICATE, |
| /*account_id=*/EmptyAccountId(), // Not used. |
| /*request_origin=*/std::string(), // Not used. |
| /*force_new_key=*/force_new_key, |
| /*key_crypto_type=*/::attestation::KEY_TYPE_RSA, |
| /*key_name=*/kEnterpriseEnrollmentKey, |
| /*profile_specific_data=*/absl::nullopt, |
| /*callback=*/ |
| base::BindOnce( |
| [](const base::RepeatingCallback<void(const std::string&)> on_success, |
| const base::RepeatingCallback<void(AttestationStatus)> on_failure, |
| const base::Location& from_here, AttestationStatus status, |
| const std::string& data) { |
| DBusPrivacyCACallback(on_success, on_failure, from_here, status, |
| std::move(data)); |
| }, |
| base::BindRepeating( |
| &EnrollmentCertificateUploaderImpl::CheckCertificateExpiry, |
| weak_factory_.GetWeakPtr()), |
| base::BindRepeating( |
| &EnrollmentCertificateUploaderImpl::HandleGetCertificateFailure, |
| weak_factory_.GetWeakPtr()), |
| FROM_HERE)); |
| } |
| |
| void EnrollmentCertificateUploaderImpl::HandleGetCertificateFailure( |
| AttestationStatus status) { |
| if (status == ATTESTATION_SERVER_BAD_REQUEST_FAILURE) { |
| LOG(ERROR) |
| << "Failed to fetch Enterprise Enrollment Certificate: bad request."; |
| RunCallbacks(Status::kFailedToFetch); |
| return; |
| } |
| |
| LOG(WARNING) << "Failed to fetch Enterprise Enrollment Certificate."; |
| |
| if (!Reschedule()) { |
| RunCallbacks(Status::kFailedToFetch); |
| } |
| } |
| |
| void EnrollmentCertificateUploaderImpl::CheckCertificateExpiry( |
| const std::string& pem_certificate_chain) { |
| // Expiry threshold is 0 so no expiring soon certificates. The reason is that |
| // enrollmen certificates expire in 1 day so there's no anyhow optimal |
| // threshold to catch expiring certificates. Worst case scenario: upload |
| // expring certificate on start-up and re-upload new one on demand. |
| const CertificateExpiryStatus cert_status = |
| ::ash::attestation::CheckCertificateExpiry( |
| pem_certificate_chain, |
| /*expiry_threshold=*/base::TimeDelta()); |
| switch (cert_status) { |
| case CertificateExpiryStatus::kExpiringSoon: |
| case CertificateExpiryStatus::kExpired: |
| LOG(WARNING) << "Existing certificate has expired."; |
| has_already_uploaded_ = false; |
| GetCertificate(/*force_new_key=*/true); |
| return; |
| case CertificateExpiryStatus::kValid: |
| case CertificateExpiryStatus::kInvalidPemChain: |
| case CertificateExpiryStatus::kInvalidX509: |
| // kInvalidPemChain and kInvalidX509 are not handled intentionally. |
| // 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_IF(ERROR, cert_status != CertificateExpiryStatus::kValid) |
| << "Failed to parse certificate, cannot check expiry: " |
| << CertificateExpiryStatusToString(cert_status); |
| UploadCertificateIfNeeded(pem_certificate_chain); |
| return; |
| } |
| |
| NOTREACHED() << "Unknown certificate status"; |
| } |
| |
| void EnrollmentCertificateUploaderImpl::UploadCertificateIfNeeded( |
| const std::string& pem_certificate_chain) { |
| if (has_already_uploaded_) { |
| RunCallbacks(Status::kSuccess); |
| return; |
| } |
| |
| if (!policy_client_->is_registered()) { |
| LOG(ERROR) << "CloudPolicyClient not registered."; |
| RunCallbacks(Status::kInvalidClient); |
| return; |
| } |
| |
| policy_client_->UploadEnterpriseEnrollmentCertificate( |
| pem_certificate_chain, |
| base::BindOnce(&EnrollmentCertificateUploaderImpl::OnUploadComplete, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void EnrollmentCertificateUploaderImpl::OnUploadComplete( |
| policy::CloudPolicyClient::Result result) { |
| if (result.IsSuccess()) { |
| has_already_uploaded_ = true; |
| if (num_retries_ != 0) { |
| LOG(WARNING) << "Enterprise Enrollment Certificate uploaded to DMServer " |
| "after retries: " |
| << num_retries_; |
| } else { |
| VLOG(1) << "Enterprise Enrollment Certificate uploaded to DMServer."; |
| } |
| RunCallbacks(Status::kSuccess); |
| return; |
| } |
| |
| LOG(WARNING) |
| << "Failed to upload Enterprise Enrollment Certificate to DMServer."; |
| |
| if (!Reschedule()) { |
| RunCallbacks(Status::kFailedToUpload); |
| } |
| } |
| |
| bool EnrollmentCertificateUploaderImpl::Reschedule() { |
| if (num_retries_ >= retry_limit_) { |
| LOG(ERROR) << "Retry limit exceeded to fetch enrollment certificate."; |
| return false; |
| } |
| |
| ++num_retries_; |
| |
| content::GetUIThreadTaskRunner({})->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&EnrollmentCertificateUploaderImpl::Start, |
| weak_factory_.GetWeakPtr()), |
| retry_delay_); |
| return true; |
| } |
| |
| void EnrollmentCertificateUploaderImpl::RunCallbacks(Status status) { |
| std::queue<UploadCallback> callbacks; |
| callbacks.swap(callbacks_); |
| for (; !callbacks.empty(); callbacks.pop()) |
| std::move(callbacks.front()).Run(status); |
| } |
| |
| } // namespace ash::attestation |