blob: 9c19421a2d479aadddf1d6ff9b35f0f3d42e6053 [file] [log] [blame]
// 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