// 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 "base/bind.h"
#include "chromeos/cryptohome/async_method_caller.h"
#include "chromeos/dbus/cryptohome_client.h"
namespace chromeos {
namespace attestation {
namespace {
// Redirects to one of three callbacks based on a boolean value and dbus call
// status.
//
// Parameters
// on_true - Called when status=succes and value=true.
// on_false - Called when status=success and value=false.
// on_fail - Called when status=failure.
// status - The D-Bus operation status.
// value - The value returned by the D-Bus operation.
void DBusBoolRedirectCallback(const base::Closure& on_true,
const base::Closure& on_false,
const base::Closure& on_fail,
DBusMethodCallStatus status,
bool value) {
if (status != DBUS_METHOD_CALL_SUCCESS) {
LOG(ERROR) << "Attestation: Failed to query enrollment state.";
if (!on_fail.is_null())
on_fail.Run();
return;
}
const base::Closure& task = value ? on_true : on_false;
if (!task.is_null())
task.Run();
}
void DBusDataMethodCallback(
const AttestationFlow::CertificateCallback& callback,
DBusMethodCallStatus status,
bool result,
const std::string& data) {
if (status != DBUS_METHOD_CALL_SUCCESS) {
LOG(ERROR) << "Attestation: DBus data operation failed.";
if (!callback.is_null())
callback.Run(false, "");
return;
}
if (!callback.is_null())
callback.Run(result, data);
}
AttestationKeyType GetKeyTypeForProfile(
AttestationCertificateProfile profile) {
switch (profile) {
case PROFILE_ENTERPRISE_MACHINE_CERTIFICATE:
return KEY_DEVICE;
case PROFILE_ENTERPRISE_USER_CERTIFICATE:
case PROFILE_CONTENT_PROTECTION_CERTIFICATE:
return KEY_USER;
}
NOTREACHED();
return KEY_USER;
}
std::string GetKeyNameForProfile(AttestationCertificateProfile profile,
const std::string& origin) {
switch (profile) {
case PROFILE_ENTERPRISE_MACHINE_CERTIFICATE:
return kEnterpriseMachineKey;
case PROFILE_ENTERPRISE_USER_CERTIFICATE:
return kEnterpriseUserKey;
case PROFILE_CONTENT_PROTECTION_CERTIFICATE:
return std::string(kContentProtectionKeyPrefix) + origin;
}
NOTREACHED();
return "";
}
} // namespace
AttestationFlow::AttestationFlow(cryptohome::AsyncMethodCaller* async_caller,
CryptohomeClient* cryptohome_client,
scoped_ptr<ServerProxy> server_proxy)
: async_caller_(async_caller),
cryptohome_client_(cryptohome_client),
server_proxy_(server_proxy.Pass()),
weak_factory_(this) {
}
AttestationFlow::~AttestationFlow() {
}
void AttestationFlow::GetCertificate(
AttestationCertificateProfile certificate_profile,
const std::string& user_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.
base::Closure do_cert_request = base::Bind(
&AttestationFlow::StartCertificateRequest,
weak_factory_.GetWeakPtr(),
certificate_profile,
user_id,
request_origin,
force_new_key,
callback);
base::Closure on_enroll_failure = base::Bind(callback, false, "");
base::Closure do_enroll = base::Bind(&AttestationFlow::StartEnroll,
weak_factory_.GetWeakPtr(),
on_enroll_failure,
do_cert_request);
cryptohome_client_->TpmAttestationIsEnrolled(base::Bind(
&DBusBoolRedirectCallback,
do_cert_request, // If enrolled, proceed with cert request.
do_enroll, // If not enrolled, initiate enrollment.
on_enroll_failure));
}
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(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(
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 std::string& user_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(
certificate_profile,
user_id,
request_origin,
base::Bind(&AttestationFlow::SendCertificateRequestToPCA,
weak_factory_.GetWeakPtr(),
key_type,
user_id,
key_name,
callback));
} else {
// If the key already exists, query the existing certificate.
base::Closure on_key_exists = base::Bind(
&AttestationFlow::GetExistingCertificate,
weak_factory_.GetWeakPtr(),
key_type,
user_id,
key_name,
callback);
// If the key does not exist, call this method back with |generate_new_key|
// set to true.
base::Closure on_key_not_exists = base::Bind(
&AttestationFlow::StartCertificateRequest,
weak_factory_.GetWeakPtr(),
certificate_profile,
user_id,
request_origin,
true,
callback);
cryptohome_client_->TpmAttestationDoesKeyExist(
key_type,
user_id,
key_name,
base::Bind(&DBusBoolRedirectCallback,
on_key_exists,
on_key_not_exists,
base::Bind(callback, false, "")));
}
}
void AttestationFlow::SendCertificateRequestToPCA(
AttestationKeyType key_type,
const std::string& user_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(false, "");
return;
}
// Send the request to the Privacy CA.
server_proxy_->SendCertificateRequest(
data,
base::Bind(&AttestationFlow::SendCertificateResponseToDaemon,
weak_factory_.GetWeakPtr(),
key_type,
user_id,
key_name,
callback));
}
void AttestationFlow::SendCertificateResponseToDaemon(
AttestationKeyType key_type,
const std::string& user_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(false, "");
return;
}
// Forward the response to the attestation service to complete the operation.
async_caller_->AsyncTpmAttestationFinishCertRequest(data,
key_type,
user_id,
key_name,
base::Bind(callback));
}
void AttestationFlow::GetExistingCertificate(
AttestationKeyType key_type,
const std::string& user_id,
const std::string& key_name,
const CertificateCallback& callback) {
cryptohome_client_->TpmAttestationGetCertificate(
key_type,
user_id,
key_name,
base::Bind(&DBusDataMethodCallback, callback));
}
} // namespace attestation
} // namespace chromeos