chromium/chromeos/ash/components/attestation/attestation_flow.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/attestation/attestation_flow.h"

#include <algorithm>
#include <optional>
#include <utility>

#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/timer/timer.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
#include "chromeos/ash/components/dbus/attestation/attestation_ca.pb.h"
#include "chromeos/ash/components/dbus/attestation/attestation_client.h"
#include "chromeos/ash/components/dbus/attestation/interface.pb.h"
#include "chromeos/ash/components/dbus/constants/attestation_constants.h"
#include "components/account_id/account_id.h"

namespace ash {
namespace attestation {

namespace {

std::optional<::attestation::CertificateProfile> ProfileToAttestationProtoEnum(
    AttestationCertificateProfile p) {
  switch (p) {
    case PROFILE_ENTERPRISE_MACHINE_CERTIFICATE:
      return ::attestation::CertificateProfile::ENTERPRISE_MACHINE_CERTIFICATE;
    case PROFILE_ENTERPRISE_USER_CERTIFICATE:
      return ::attestation::CertificateProfile::ENTERPRISE_USER_CERTIFICATE;
    case PROFILE_CONTENT_PROTECTION_CERTIFICATE:
      return ::attestation::CertificateProfile::CONTENT_PROTECTION_CERTIFICATE;
    case PROFILE_ENTERPRISE_ENROLLMENT_CERTIFICATE:
      return ::attestation::CertificateProfile::
          ENTERPRISE_ENROLLMENT_CERTIFICATE;
    case PROFILE_SOFT_BIND_CERTIFICATE:
      return ::attestation::CertificateProfile::SOFT_BIND_CERTIFICATE;
    case PROFILE_DEVICE_SETUP_CERTIFICATE:
      return ::attestation::CertificateProfile::DEVICE_SETUP_CERTIFICATE;
    case PROFILE_DEVICE_TRUST_USER_CERTIFICATE:
      return ::attestation::CertificateProfile::DEVICE_TRUST_USER_CERTIFICATE;
  }
  return {};
}

::attestation::ACAType ToAcaType(PrivacyCAType type) {
  switch (type) {
    case DEFAULT_PCA:
      return ::attestation::DEFAULT_ACA;
    case TEST_PCA:
      return ::attestation::TEST_ACA;
  }
  LOG(DFATAL) << "Unknown type to convert: " << type;
  return ::attestation::DEFAULT_ACA;
}

// 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;

}  // namespace

AttestationKeyType AttestationFlow::GetKeyTypeForProfile(
    AttestationCertificateProfile certificate_profile) {
  switch (certificate_profile) {
    case PROFILE_ENTERPRISE_MACHINE_CERTIFICATE:
    case PROFILE_ENTERPRISE_ENROLLMENT_CERTIFICATE:
    case PROFILE_DEVICE_SETUP_CERTIFICATE:
    case PROFILE_DEVICE_TRUST_USER_CERTIFICATE:
      return KEY_DEVICE;
    case PROFILE_ENTERPRISE_USER_CERTIFICATE:
    case PROFILE_CONTENT_PROTECTION_CERTIFICATE:
    case PROFILE_SOFT_BIND_CERTIFICATE:
      return KEY_USER;
  }
  NOTREACHED_IN_MIGRATION();
  return KEY_USER;
}

AttestationFlowLegacy::AttestationFlowLegacy(
    std::unique_ptr<ServerProxy> server_proxy)
    : attestation_client_(AttestationClient::Get()),
      server_proxy_(std::move(server_proxy)),
      ready_timeout_(base::Seconds(kReadyTimeoutInSeconds)),
      retry_delay_(base::Milliseconds(kRetryDelayInMilliseconds)) {}

AttestationFlowLegacy::~AttestationFlowLegacy() = default;

void AttestationFlowLegacy::GetCertificate(
    AttestationCertificateProfile certificate_profile,
    const AccountId& account_id,
    const std::string& request_origin,
    bool force_new_key,
    ::attestation::KeyType key_crypto_type,
    const std::string& key_name,
    const std::optional<CertProfileSpecificData>& profile_specific_data,
    CertificateCallback callback) {
  DCHECK(!key_name.empty());

  EnrollCallback start_certificate_request =
      base::BindOnce(&AttestationFlowLegacy::StartCertificateRequest,
                     weak_factory_.GetWeakPtr(), certificate_profile,
                     account_id, request_origin, force_new_key, key_crypto_type,
                     key_name, profile_specific_data, 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.
  ::attestation::GetStatusRequest status_request;
  status_request.set_extended_status(true);
  attestation_client_->GetStatus(
      status_request,
      base::BindOnce(&AttestationFlowLegacy::OnEnrollmentCheckComplete,
                     weak_factory_.GetWeakPtr(), certificate_profile,
                     std::move(start_certificate_request)));
}

void AttestationFlowLegacy::OnEnrollmentCheckComplete(
    AttestationCertificateProfile certificate_profile,
    EnrollCallback callback,
    const ::attestation::GetStatusReply& reply) {
  if (reply.status() != ::attestation::STATUS_SUCCESS) {
    LOG(ERROR) << "Attestation: Failed to check enrollment state. Status: "
               << reply.status();
    std::move(callback).Run(EnrollState::kError);
    return;
  }

  if (reply.enrolled()) {
    std::move(callback).Run(EnrollState::kEnrolled);
    return;
  }

  // The verified boot state is required for soft bind certificates.
  if (certificate_profile ==
          AttestationCertificateProfile::PROFILE_SOFT_BIND_CERTIFICATE &&
      !reply.verified_boot()) {
    LOG(ERROR) << "Attestation: Cannot create soft bind certificate without "
                  "verified boot.";
    std::move(callback).Run(EnrollState::kError);
    return;
  }

  // The device is not enrolled; check if it supports attestation.
  GetFeatures(std::move(callback));
}

void AttestationFlowLegacy::GetFeatures(EnrollCallback callback) {
  attestation_client_->GetFeatures(
      ::attestation::GetFeaturesRequest(),
      base::BindOnce(&AttestationFlowLegacy::OnGetFeaturesComplete,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
}

void AttestationFlowLegacy::OnGetFeaturesComplete(
    EnrollCallback callback,
    const ::attestation::GetFeaturesReply& reply) {
  if (reply.status() != ::attestation::STATUS_SUCCESS) {
    LOG(ERROR) << "Attestation: Failed to get features; status: "
               << reply.status();
    std::move(callback).Run(EnrollState::kError);
    return;
  }

  if (reply.is_available()) {
    // Check if the device is enrollment prepared.
    base::TimeTicks end_time = base::TimeTicks::Now() + ready_timeout_;
    WaitForAttestationPrepared(end_time, std::move(callback));
  } else {
    std::move(callback).Run(EnrollState::kNotAvailable);
  }
}

void AttestationFlowLegacy::WaitForAttestationPrepared(
    base::TimeTicks end_time,
    EnrollCallback callback) {
  ::attestation::GetEnrollmentPreparationsRequest request;
  attestation_client_->GetEnrollmentPreparations(
      request, base::BindOnce(&AttestationFlowLegacy::OnPreparedCheckComplete,
                              weak_factory_.GetWeakPtr(), end_time,
                              std::move(callback)));
}

void AttestationFlowLegacy::OnPreparedCheckComplete(
    base::TimeTicks end_time,
    EnrollCallback callback,
    const ::attestation::GetEnrollmentPreparationsReply& reply) {
  if (AttestationClient::IsAttestationPrepared(reply)) {
    // Get the attestation service to create a Privacy CA enrollment request.
    ::attestation::CreateEnrollRequestRequest request;
    request.set_aca_type(ToAcaType(server_proxy_->GetType()));
    AttestationClient::Get()->CreateEnrollRequest(
        request,
        base::BindOnce(&AttestationFlowLegacy::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::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&AttestationFlowLegacy::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(EnrollState::kError);
}

void AttestationFlowLegacy::SendEnrollRequestToPCA(
    EnrollCallback callback,
    const ::attestation::CreateEnrollRequestReply& reply) {
  if (reply.status() != ::attestation::STATUS_SUCCESS) {
    LOG(ERROR) << "Attestation: Failed to create enroll request; status: "
               << reply.status();
    std::move(callback).Run(EnrollState::kError);
    return;
  }

  // Send the request to the Privacy CA.
  server_proxy_->SendEnrollRequest(
      reply.pca_request(),
      base::BindOnce(&AttestationFlowLegacy::SendEnrollResponseToDaemon,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
}

void AttestationFlowLegacy::SendEnrollResponseToDaemon(
    EnrollCallback callback,
    bool success,
    const std::string& data) {
  if (!success) {
    LOG(ERROR) << "Attestation: Enroll request failed.";
    std::move(callback).Run(EnrollState::kError);
    return;
  }

  // Forward the response to the attestation service to complete enrollment.
  ::attestation::FinishEnrollRequest request;
  request.set_pca_response(data);
  request.set_aca_type(ToAcaType(server_proxy_->GetType()));
  AttestationClient::Get()->FinishEnroll(
      request, base::BindOnce(&AttestationFlowLegacy::OnEnrollComplete,
                              weak_factory_.GetWeakPtr(), std::move(callback)));
}

void AttestationFlowLegacy::OnEnrollComplete(
    EnrollCallback callback,
    const ::attestation::FinishEnrollReply& reply) {
  if (reply.status() != ::attestation::STATUS_SUCCESS) {
    LOG(ERROR) << "Attestation: Failed to complete enrollment; status: "
               << reply.status();
    std::move(callback).Run(EnrollState::kError);
    return;
  }

  std::move(callback).Run(EnrollState::kEnrolled);
}

void AttestationFlowLegacy::StartCertificateRequest(
    AttestationCertificateProfile certificate_profile,
    const AccountId& account_id,
    const std::string& request_origin,
    bool generate_new_key,
    ::attestation::KeyType key_crypto_type,
    const std::string& key_name,
    const std::optional<CertProfileSpecificData>& profile_specific_data,
    CertificateCallback callback,
    EnrollState enroll_state) {
  switch (enroll_state) {
    case EnrollState::kError:
      std::move(callback).Run(ATTESTATION_UNSPECIFIED_FAILURE, "");
      return;

    case EnrollState::kNotAvailable:
      std::move(callback).Run(ATTESTATION_NOT_AVAILABLE, "");
      return;

    case EnrollState::kEnrolled:
      break;
  }

  AttestationKeyType key_type = GetKeyTypeForProfile(certificate_profile);
  if (generate_new_key) {
    // Get the attestation service to create a Privacy CA certificate request.
    const std::optional<::attestation::CertificateProfile> attestation_profile =
        ProfileToAttestationProtoEnum(certificate_profile);
    if (!attestation_profile) {
      LOG(DFATAL) << "Attestation: Unrecognized profile type: "
                  << certificate_profile;
      return;
    }

    ::attestation::CreateCertificateRequestRequest request;
    if (key_type == KEY_USER) {
      request.set_username(cryptohome::Identification(account_id).id());
    }
    request.set_certificate_profile(*attestation_profile);
    request.set_request_origin(request_origin);
    request.set_key_type(key_crypto_type);
    request.set_aca_type(ToAcaType(server_proxy_->GetType()));

    if (attestation_profile ==
        ::attestation::CertificateProfile::DEVICE_SETUP_CERTIFICATE) {
      DCHECK(profile_specific_data.has_value())
          << "profile_specific_data must be provided for "
             "DEVICE_SETUP_CERTIFICATE";
      DCHECK(absl::holds_alternative<
             ::attestation::DeviceSetupCertificateRequestMetadata>(
          profile_specific_data.value()))
          << "profile_specific_data must be of type "
             "::attestation::DeviceSetupCertificateRequestMetadata";

      request.mutable_device_setup_certificate_request_metadata()->set_id(
          absl::get<::attestation::DeviceSetupCertificateRequestMetadata>(
              profile_specific_data.value())
              .id());
      request.mutable_device_setup_certificate_request_metadata()
          ->set_content_binding(
              absl::get<::attestation::DeviceSetupCertificateRequestMetadata>(
                  profile_specific_data.value())
                  .content_binding());
    }

    attestation_client_->CreateCertificateRequest(
        request,
        base::BindOnce(&AttestationFlowLegacy::SendCertificateRequestToPCA,
                       weak_factory_.GetWeakPtr(), key_type, account_id,
                       key_name, std::move(callback)));
    return;
  }

  ::attestation::GetKeyInfoRequest request;
  if (key_type == KEY_USER) {
    request.set_username(cryptohome::Identification(account_id).id());
  }
  request.set_key_label(key_name);
  attestation_client_->GetKeyInfo(
      request,
      base::BindOnce(&AttestationFlowLegacy::OnGetKeyInfoComplete,
                     weak_factory_.GetWeakPtr(), certificate_profile,
                     account_id, request_origin, key_crypto_type, key_name,
                     key_type, profile_specific_data, std::move(callback)));
}

void AttestationFlowLegacy::OnGetKeyInfoComplete(
    AttestationCertificateProfile certificate_profile,
    const AccountId& account_id,
    const std::string& request_origin,
    ::attestation::KeyType key_crypto_type,
    const std::string& key_name,
    AttestationKeyType key_type,
    const std::optional<CertProfileSpecificData>& profile_specific_data,
    CertificateCallback callback,
    const ::attestation::GetKeyInfoReply& reply) {
  // If the key already exists, return the existing certificate.
  if (reply.status() == ::attestation::STATUS_SUCCESS) {
    std::move(callback).Run(ATTESTATION_SUCCESS, reply.certificate());
    return;
  }

  // If the key does not exist, call this method back with |generate_new_key|
  // set to true.
  if (reply.status() == ::attestation::STATUS_INVALID_PARAMETER) {
    StartCertificateRequest(certificate_profile, account_id, request_origin,
                            /*generate_new_key=*/true, key_crypto_type,
                            key_name, profile_specific_data,
                            std::move(callback), EnrollState::kEnrolled);
    return;
  }

  // Otherwise the key info query fails.
  LOG(ERROR) << "Attestation: Failed to check for existence of key; status: "
             << reply.status() << ".";
  std::move(callback).Run(ATTESTATION_UNSPECIFIED_FAILURE, "");
}

void AttestationFlowLegacy::SendCertificateRequestToPCA(
    AttestationKeyType key_type,
    const AccountId& account_id,
    const std::string& key_name,
    CertificateCallback callback,
    const ::attestation::CreateCertificateRequestReply& reply) {
  if (reply.status() != ::attestation::STATUS_SUCCESS) {
    LOG(ERROR) << "Attestation: Failed to create certificate request. Status: "
               << reply.status();
    std::move(callback).Run(ATTESTATION_UNSPECIFIED_FAILURE, "");
    return;
  }

  // Send the request to the Privacy CA.
  server_proxy_->SendCertificateRequest(
      reply.pca_request(),
      base::BindOnce(&AttestationFlowLegacy::SendCertificateResponseToDaemon,
                     weak_factory_.GetWeakPtr(), key_type, account_id, key_name,
                     std::move(callback)));
}

void AttestationFlowLegacy::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;
  }

  ::attestation::FinishCertificateRequestRequest request;
  if (key_type == KEY_USER) {
    request.set_username(cryptohome::Identification(account_id).id());
  }
  request.set_key_label(key_name);
  request.set_pca_response(data);
  AttestationClient::Get()->FinishCertificateRequest(
      request, base::BindOnce(&AttestationFlowLegacy::OnCertRequestFinished,
                              weak_factory_.GetWeakPtr(), std::move(callback)));
}

void AttestationFlowLegacy::OnCertRequestFinished(
    CertificateCallback callback,
    const ::attestation::FinishCertificateRequestReply& reply) {
  if (reply.status() == ::attestation::STATUS_SUCCESS) {
    std::move(callback).Run(ATTESTATION_SUCCESS, reply.certificate());
  } else {
    LOG(ERROR) << "Failed to finish certificate request; status: "
               << reply.status();
    std::move(callback).Run(ATTESTATION_SERVER_BAD_REQUEST_FAILURE,
                            /*pem_certificate_chain=*/"");
  }
}

ServerProxy::~ServerProxy() = default;

PrivacyCAType ServerProxy::GetType() {
  return DEFAULT_PCA;
}

}  // namespace attestation
}  // namespace ash