blob: 852d0f166c8698638844d028cf5ebe123646a2c3 [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/optional.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/timer/timer.h"
#include "chromeos/cryptohome/async_method_caller.h"
#include "chromeos/cryptohome/cryptohome_parameters.h"
#include "chromeos/dbus/cryptohome/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;
void DBusCertificateMethodCallback(
AttestationFlow::CertificateCallback callback,
base::Optional<CryptohomeClient::TpmAttestationDataResult> result) {
if (!result.has_value()) {
LOG(ERROR) << "Attestation: DBus data operation failed.";
std::move(callback).Run(ATTESTATION_UNSPECIFIED_FAILURE, "");
return;
}
if (callback) {
std::move(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)) {}
AttestationFlow::~AttestationFlow() = default;
void AttestationFlow::GetCertificate(
AttestationCertificateProfile certificate_profile,
const AccountId& account_id,
const std::string& request_origin,
bool force_new_key,
const std::string& key_name,
CertificateCallback callback) {
std::string attestation_key_name =
!key_name.empty()
? key_name
: GetKeyNameForProfile(certificate_profile, request_origin);
base::OnceCallback<void(bool)> start_certificate_request = base::BindOnce(
&AttestationFlow::StartCertificateRequest, weak_factory_.GetWeakPtr(),
certificate_profile, account_id, request_origin, force_new_key,
attestation_key_name, std::move(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.
cryptohome_client_->TpmAttestationIsEnrolled(base::BindOnce(
&AttestationFlow::OnEnrollmentCheckComplete, weak_factory_.GetWeakPtr(),
std::move(start_certificate_request)));
}
void AttestationFlow::OnEnrollmentCheckComplete(
base::OnceCallback<void(bool)> callback,
base::Optional<bool> result) {
if (!result) {
LOG(ERROR) << "Attestation: Failed to check enrollment state.";
std::move(callback).Run(false);
return;
}
if (*result) {
std::move(callback).Run(true);
return;
}
base::TimeTicks end_time = base::TimeTicks::Now() + ready_timeout_;
WaitForAttestationPrepared(end_time, std::move(callback));
}
void AttestationFlow::WaitForAttestationPrepared(
base::TimeTicks end_time,
base::OnceCallback<void(bool)> callback) {
cryptohome_client_->TpmAttestationIsPrepared(base::BindOnce(
&AttestationFlow::OnPreparedCheckComplete, weak_factory_.GetWeakPtr(),
end_time, std::move(callback)));
}
void AttestationFlow::OnPreparedCheckComplete(
base::TimeTicks end_time,
base::OnceCallback<void(bool)> callback,
base::Optional<bool> result) {
if (!result) {
LOG(ERROR) << "Attestation: Failed to check for attestation readiness";
std::move(callback).Run(false);
return;
}
if (*result) {
// Get the attestation service to create a Privacy CA enrollment request.
async_caller_->AsyncTpmAttestationCreateEnrollRequest(
server_proxy_->GetType(),
base::BindOnce(&AttestationFlow::SendEnrollRequestToPCA,
weak_factory_.GetWeakPtr(), std::move(callback)));
return;
}
if (base::TimeTicks::Now() < end_time) {
LOG(WARNING) << "Attestation: Not prepared yet."
<< " Retrying in " << retry_delay_ << ".";
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&AttestationFlow::WaitForAttestationPrepared,
weak_factory_.GetWeakPtr(), end_time,
std::move(callback)),
retry_delay_);
return;
}
LOG(ERROR) << "Attestation: Not prepared. Giving up on retrying.";
std::move(callback).Run(false);
}
void AttestationFlow::SendEnrollRequestToPCA(
base::OnceCallback<void(bool)> callback,
bool success,
const std::string& data) {
if (!success) {
LOG(ERROR) << "Attestation: Failed to create enroll request.";
std::move(callback).Run(false);
return;
}
// Send the request to the Privacy CA.
server_proxy_->SendEnrollRequest(
data, base::BindOnce(&AttestationFlow::SendEnrollResponseToDaemon,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void AttestationFlow::SendEnrollResponseToDaemon(
base::OnceCallback<void(bool)> callback,
bool success,
const std::string& data) {
if (!success) {
LOG(ERROR) << "Attestation: Enroll request failed.";
std::move(callback).Run(false);
return;
}
// Forward the response to the attestation service to complete enrollment.
async_caller_->AsyncTpmAttestationEnroll(
server_proxy_->GetType(), data,
base::BindOnce(&AttestationFlow::OnEnrollComplete,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void AttestationFlow::OnEnrollComplete(base::OnceCallback<void(bool)> callback,
bool success,
cryptohome::MountError /*not_used*/) {
if (!success) {
LOG(ERROR) << "Attestation: Failed to complete enrollment.";
std::move(callback).Run(false);
return;
}
std::move(callback).Run(true);
}
void AttestationFlow::StartCertificateRequest(
AttestationCertificateProfile certificate_profile,
const AccountId& account_id,
const std::string& request_origin,
bool generate_new_key,
const std::string& key_name,
CertificateCallback callback,
bool enrolled) {
if (!enrolled) {
std::move(callback).Run(ATTESTATION_UNSPECIFIED_FAILURE, "");
return;
}
AttestationKeyType key_type = GetKeyTypeForProfile(certificate_profile);
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::BindOnce(&AttestationFlow::SendCertificateRequestToPCA,
weak_factory_.GetWeakPtr(), key_type, account_id,
key_name, std::move(callback)));
return;
}
cryptohome_client_->TpmAttestationDoesKeyExist(
key_type, cryptohome::CreateAccountIdentifierFromAccountId(account_id),
key_name,
base::BindOnce(&AttestationFlow::OnKeyExistCheckComplete,
weak_factory_.GetWeakPtr(), certificate_profile,
account_id, request_origin, key_name, key_type,
std::move(callback)));
}
void AttestationFlow::OnKeyExistCheckComplete(
AttestationCertificateProfile certificate_profile,
const AccountId& account_id,
const std::string& request_origin,
const std::string& key_name,
AttestationKeyType key_type,
CertificateCallback callback,
base::Optional<bool> result) {
if (!result) {
LOG(ERROR) << "Attestation: Failed to check for existence of key.";
std::move(callback).Run(ATTESTATION_UNSPECIFIED_FAILURE, "");
return;
}
// If the key already exists, query the existing certificate.
if (*result) {
GetExistingCertificate(key_type, account_id, key_name, std::move(callback));
return;
}
// If the key does not exist, call this method back with |generate_new_key|
// set to true.
StartCertificateRequest(certificate_profile, account_id, request_origin, true,
key_name, std::move(callback), true);
}
void AttestationFlow::SendCertificateRequestToPCA(AttestationKeyType key_type,
const AccountId& account_id,
const std::string& key_name,
CertificateCallback callback,
bool success,
const std::string& data) {
if (!success) {
LOG(ERROR) << "Attestation: Failed to create certificate request.";
std::move(callback).Run(ATTESTATION_UNSPECIFIED_FAILURE, "");
return;
}
// Send the request to the Privacy CA.
server_proxy_->SendCertificateRequest(
data, base::BindOnce(&AttestationFlow::SendCertificateResponseToDaemon,
weak_factory_.GetWeakPtr(), key_type, account_id,
key_name, std::move(callback)));
}
void AttestationFlow::SendCertificateResponseToDaemon(
AttestationKeyType key_type,
const AccountId& account_id,
const std::string& key_name,
CertificateCallback callback,
bool success,
const std::string& data) {
if (!success) {
LOG(ERROR) << "Attestation: Certificate request failed.";
std::move(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::BindOnce(&AttestationFlow::OnCertRequestFinished,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void AttestationFlow::OnCertRequestFinished(CertificateCallback callback,
bool success,
const std::string& data) {
if (success)
std::move(callback).Run(ATTESTATION_SUCCESS, data);
else
std::move(callback).Run(ATTESTATION_SERVER_BAD_REQUEST_FAILURE, data);
}
void AttestationFlow::GetExistingCertificate(AttestationKeyType key_type,
const AccountId& account_id,
const std::string& key_name,
CertificateCallback callback) {
cryptohome_client_->TpmAttestationGetCertificate(
key_type, cryptohome::CreateAccountIdentifierFromAccountId(account_id),
key_name,
base::BindOnce(&DBusCertificateMethodCallback, std::move(callback)));
}
ServerProxy::~ServerProxy() = default;
PrivacyCAType ServerProxy::GetType() {
return DEFAULT_PCA;
}
} // namespace attestation
} // namespace chromeos