| // Copyright (c) 2012 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 "chromeos/attestation/attestation_flow.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/message_loop/message_loop_current.h" |
| #include "base/optional.h" |
| #include "base/timer/timer.h" |
| #include "chromeos/cryptohome/async_method_caller.h" |
| #include "chromeos/cryptohome/cryptohome_parameters.h" |
| #include "chromeos/dbus/cryptohome_client.h" |
| #include "components/account_id/account_id.h" |
| |
| namespace chromeos { |
| namespace attestation { |
| |
| namespace { |
| |
| // A reasonable timeout that gives enough time for attestation to be ready, |
| // yet does not make the caller wait too long. |
| constexpr uint16_t kReadyTimeoutInSeconds = 60; |
| |
| // Delay before checking again whether the TPM has been prepared for |
| // attestation. |
| constexpr uint16_t kRetryDelayInMilliseconds = 300; |
| |
| // Redirects to one of three callbacks based on a boolean value and dbus call |
| // status. |
| // |
| // Parameters |
| // on_true - Called when status=success and value=true. |
| // on_false - Called when status=success and value=false. |
| // on_fail - Called when status=failure. |
| // result - The result returned by the D-Bus operation. |
| void DBusBoolRedirectCallback(const base::Closure& on_true, |
| const base::Closure& on_false, |
| const base::Closure& on_fail, |
| const std::string& on_fail_message, |
| base::Optional<bool> result) { |
| if (!result.has_value()) { |
| LOG(ERROR) << "Attestation: Failed to " << on_fail_message << "."; |
| if (!on_fail.is_null()) |
| on_fail.Run(); |
| return; |
| } |
| const base::Closure& task = result.value() ? on_true : on_false; |
| if (!task.is_null()) |
| task.Run(); |
| } |
| |
| void DBusCertificateMethodCallback( |
| const AttestationFlow::CertificateCallback& callback, |
| base::Optional<CryptohomeClient::TpmAttestationDataResult> result) { |
| if (!result.has_value()) { |
| LOG(ERROR) << "Attestation: DBus data operation failed."; |
| if (!callback.is_null()) |
| callback.Run(ATTESTATION_UNSPECIFIED_FAILURE, ""); |
| return; |
| } |
| if (!callback.is_null()) { |
| callback.Run( |
| result->success ? ATTESTATION_SUCCESS : ATTESTATION_UNSPECIFIED_FAILURE, |
| result->data); |
| } |
| } |
| |
| } // namespace |
| |
| AttestationKeyType AttestationFlow::GetKeyTypeForProfile( |
| AttestationCertificateProfile certificate_profile) { |
| switch (certificate_profile) { |
| case PROFILE_ENTERPRISE_MACHINE_CERTIFICATE: |
| case PROFILE_ENTERPRISE_ENROLLMENT_CERTIFICATE: |
| return KEY_DEVICE; |
| case PROFILE_ENTERPRISE_USER_CERTIFICATE: |
| case PROFILE_CONTENT_PROTECTION_CERTIFICATE: |
| return KEY_USER; |
| } |
| NOTREACHED(); |
| return KEY_USER; |
| } |
| |
| std::string AttestationFlow::GetKeyNameForProfile( |
| AttestationCertificateProfile certificate_profile, |
| const std::string& request_origin) { |
| switch (certificate_profile) { |
| case PROFILE_ENTERPRISE_MACHINE_CERTIFICATE: |
| return kEnterpriseMachineKey; |
| case PROFILE_ENTERPRISE_ENROLLMENT_CERTIFICATE: |
| return kEnterpriseEnrollmentKey; |
| case PROFILE_ENTERPRISE_USER_CERTIFICATE: |
| return kEnterpriseUserKey; |
| case PROFILE_CONTENT_PROTECTION_CERTIFICATE: |
| return std::string(kContentProtectionKeyPrefix) + request_origin; |
| } |
| NOTREACHED(); |
| return ""; |
| } |
| |
| AttestationFlow::AttestationFlow(cryptohome::AsyncMethodCaller* async_caller, |
| CryptohomeClient* cryptohome_client, |
| std::unique_ptr<ServerProxy> server_proxy) |
| : async_caller_(async_caller), |
| cryptohome_client_(cryptohome_client), |
| server_proxy_(std::move(server_proxy)), |
| ready_timeout_(base::TimeDelta::FromSeconds(kReadyTimeoutInSeconds)), |
| retry_delay_( |
| base::TimeDelta::FromMilliseconds(kRetryDelayInMilliseconds)), |
| weak_factory_(this) {} |
| |
| AttestationFlow::~AttestationFlow() = default; |
| |
| void AttestationFlow::GetCertificate( |
| AttestationCertificateProfile certificate_profile, |
| const AccountId& account_id, |
| const std::string& request_origin, |
| bool force_new_key, |
| const CertificateCallback& callback) { |
| // If this device has not enrolled with the Privacy CA, we need to do that |
| // first. Once enrolled we can proceed with the certificate request. |
| const base::Closure do_cert_request = base::Bind( |
| &AttestationFlow::StartCertificateRequest, weak_factory_.GetWeakPtr(), |
| certificate_profile, account_id, request_origin, force_new_key, callback); |
| const base::RepeatingClosure on_failure = |
| base::BindRepeating(callback, ATTESTATION_UNSPECIFIED_FAILURE, ""); |
| const base::Closure initiate_enroll = base::Bind( |
| &AttestationFlow::WaitForAttestationReadyAndStartEnroll, |
| weak_factory_.GetWeakPtr(), base::TimeTicks::Now() + ready_timeout_, |
| on_failure, |
| base::Bind(&AttestationFlow::StartEnroll, weak_factory_.GetWeakPtr(), |
| on_failure, do_cert_request)); |
| cryptohome_client_->TpmAttestationIsEnrolled(base::BindOnce( |
| &DBusBoolRedirectCallback, |
| do_cert_request, // If enrolled, proceed with cert request. |
| initiate_enroll, // If not enrolled, initiate enrollment. |
| on_failure, "check enrollment state")); |
| } |
| |
| void AttestationFlow::WaitForAttestationReadyAndStartEnroll( |
| base::TimeTicks end_time, |
| const base::Closure& on_failure, |
| const base::Closure& next_task) { |
| const base::Closure retry_initiate_enroll = |
| base::Bind(&AttestationFlow::CheckAttestationReadyAndReschedule, |
| weak_factory_.GetWeakPtr(), end_time, on_failure, next_task); |
| cryptohome_client_->TpmAttestationIsPrepared(base::BindOnce( |
| &DBusBoolRedirectCallback, next_task, retry_initiate_enroll, on_failure, |
| "check for attestation readiness")); |
| } |
| |
| void AttestationFlow::StartEnroll(const base::Closure& on_failure, |
| const base::Closure& next_task) { |
| // Get the attestation service to create a Privacy CA enrollment request. |
| async_caller_->AsyncTpmAttestationCreateEnrollRequest( |
| server_proxy_->GetType(), |
| base::Bind(&AttestationFlow::SendEnrollRequestToPCA, |
| weak_factory_.GetWeakPtr(), on_failure, next_task)); |
| } |
| |
| void AttestationFlow::SendEnrollRequestToPCA(const base::Closure& on_failure, |
| const base::Closure& next_task, |
| bool success, |
| const std::string& data) { |
| if (!success) { |
| LOG(ERROR) << "Attestation: Failed to create enroll request."; |
| if (!on_failure.is_null()) |
| on_failure.Run(); |
| return; |
| } |
| |
| // Send the request to the Privacy CA. |
| server_proxy_->SendEnrollRequest( |
| data, base::Bind(&AttestationFlow::SendEnrollResponseToDaemon, |
| weak_factory_.GetWeakPtr(), on_failure, next_task)); |
| } |
| |
| void AttestationFlow::SendEnrollResponseToDaemon( |
| const base::Closure& on_failure, |
| const base::Closure& next_task, |
| bool success, |
| const std::string& data) { |
| if (!success) { |
| LOG(ERROR) << "Attestation: Enroll request failed."; |
| if (!on_failure.is_null()) |
| on_failure.Run(); |
| return; |
| } |
| |
| // Forward the response to the attestation service to complete enrollment. |
| async_caller_->AsyncTpmAttestationEnroll( |
| server_proxy_->GetType(), data, |
| base::Bind(&AttestationFlow::OnEnrollComplete, weak_factory_.GetWeakPtr(), |
| on_failure, next_task)); |
| } |
| |
| void AttestationFlow::OnEnrollComplete(const base::Closure& on_failure, |
| const base::Closure& next_task, |
| bool success, |
| cryptohome::MountError /*not_used*/) { |
| if (!success) { |
| LOG(ERROR) << "Attestation: Failed to complete enrollment."; |
| if (!on_failure.is_null()) |
| on_failure.Run(); |
| return; |
| } |
| |
| // Enrollment has successfully completed, we can move on to whatever is next. |
| if (!next_task.is_null()) |
| next_task.Run(); |
| } |
| |
| void AttestationFlow::StartCertificateRequest( |
| AttestationCertificateProfile certificate_profile, |
| const AccountId& account_id, |
| const std::string& request_origin, |
| bool generate_new_key, |
| const CertificateCallback& callback) { |
| AttestationKeyType key_type = GetKeyTypeForProfile(certificate_profile); |
| std::string key_name = |
| GetKeyNameForProfile(certificate_profile, request_origin); |
| if (generate_new_key) { |
| // Get the attestation service to create a Privacy CA certificate request. |
| async_caller_->AsyncTpmAttestationCreateCertRequest( |
| server_proxy_->GetType(), certificate_profile, |
| cryptohome::Identification(account_id), request_origin, |
| base::Bind(&AttestationFlow::SendCertificateRequestToPCA, |
| weak_factory_.GetWeakPtr(), key_type, account_id, key_name, |
| callback)); |
| } else { |
| // If the key already exists, query the existing certificate. |
| const base::Closure on_key_exists = base::Bind( |
| &AttestationFlow::GetExistingCertificate, weak_factory_.GetWeakPtr(), |
| key_type, account_id, key_name, callback); |
| // If the key does not exist, call this method back with |generate_new_key| |
| // set to true. |
| const base::Closure on_key_not_exists = base::Bind( |
| &AttestationFlow::StartCertificateRequest, weak_factory_.GetWeakPtr(), |
| certificate_profile, account_id, request_origin, true, callback); |
| cryptohome_client_->TpmAttestationDoesKeyExist( |
| key_type, cryptohome::CreateAccountIdentifierFromAccountId(account_id), |
| key_name, |
| base::BindOnce( |
| &DBusBoolRedirectCallback, on_key_exists, on_key_not_exists, |
| base::BindRepeating(callback, ATTESTATION_UNSPECIFIED_FAILURE, ""), |
| "check for existence of attestation key")); |
| } |
| } |
| |
| void AttestationFlow::SendCertificateRequestToPCA( |
| AttestationKeyType key_type, |
| const AccountId& account_id, |
| const std::string& key_name, |
| const CertificateCallback& callback, |
| bool success, |
| const std::string& data) { |
| if (!success) { |
| LOG(ERROR) << "Attestation: Failed to create certificate request."; |
| if (!callback.is_null()) |
| callback.Run(ATTESTATION_UNSPECIFIED_FAILURE, ""); |
| return; |
| } |
| |
| // Send the request to the Privacy CA. |
| server_proxy_->SendCertificateRequest( |
| data, base::Bind(&AttestationFlow::SendCertificateResponseToDaemon, |
| weak_factory_.GetWeakPtr(), key_type, account_id, |
| key_name, callback)); |
| } |
| |
| void AttestationFlow::SendCertificateResponseToDaemon( |
| AttestationKeyType key_type, |
| const AccountId& account_id, |
| const std::string& key_name, |
| const CertificateCallback& callback, |
| bool success, |
| const std::string& data) { |
| if (!success) { |
| LOG(ERROR) << "Attestation: Certificate request failed."; |
| if (!callback.is_null()) |
| callback.Run(ATTESTATION_UNSPECIFIED_FAILURE, ""); |
| return; |
| } |
| |
| // Forward the response to the attestation service to complete the operation. |
| async_caller_->AsyncTpmAttestationFinishCertRequest( |
| data, key_type, cryptohome::Identification(account_id), key_name, |
| base::BindRepeating(&AttestationFlow::OnCertRequestFinished, |
| weak_factory_.GetWeakPtr(), callback)); |
| } |
| |
| void AttestationFlow::OnCertRequestFinished(const CertificateCallback& callback, |
| bool success, |
| const std::string& data) { |
| if (success) |
| callback.Run(ATTESTATION_SUCCESS, data); |
| else |
| callback.Run(ATTESTATION_SERVER_BAD_REQUEST_FAILURE, data); |
| } |
| |
| void AttestationFlow::GetExistingCertificate( |
| AttestationKeyType key_type, |
| const AccountId& account_id, |
| const std::string& key_name, |
| const CertificateCallback& callback) { |
| cryptohome_client_->TpmAttestationGetCertificate( |
| key_type, cryptohome::CreateAccountIdentifierFromAccountId(account_id), |
| key_name, base::BindOnce(&DBusCertificateMethodCallback, callback)); |
| } |
| |
| void AttestationFlow::CheckAttestationReadyAndReschedule( |
| base::TimeTicks end_time, |
| const base::Closure& on_failure, |
| const base::Closure& next_task) { |
| if (base::TimeTicks::Now() < end_time) { |
| LOG(WARNING) << "Attestation: Not prepared yet." |
| << " Retrying in " << retry_delay_ << "."; |
| base::MessageLoopCurrent::Get()->task_runner()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&AttestationFlow::WaitForAttestationReadyAndStartEnroll, |
| weak_factory_.GetWeakPtr(), end_time, on_failure, |
| next_task), |
| retry_delay_); |
| } else { |
| LOG(ERROR) << "Attestation: Not prepared. Giving up on retrying."; |
| if (!on_failure.is_null()) |
| on_failure.Run(); |
| } |
| } |
| |
| ServerProxy::~ServerProxy() = default; |
| |
| PrivacyCAType ServerProxy::GetType() { |
| return DEFAULT_PCA; |
| } |
| |
| } // namespace attestation |
| } // namespace chromeos |