chromium/chrome/browser/enterprise/connectors/device_trust/attestation/ash/ash_attestation_service_impl.cc

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

#include "chrome/browser/enterprise/connectors/device_trust/attestation/ash/ash_attestation_service_impl.h"

#include <memory>
#include <string>
#include <utility>

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/json/json_writer.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/ash/attestation/tpm_challenge_key_result.h"
#include "chrome/browser/ash/attestation/tpm_challenge_key_subtle.h"
#include "chrome/browser/ash/attestation/tpm_challenge_key_with_timeout.h"
#include "chrome/browser/enterprise/connectors/device_trust/attestation/common/attestation_utils.h"
#include "chrome/browser/enterprise/connectors/device_trust/common/common_types.h"
#include "chrome/browser/enterprise/connectors/device_trust/common/metrics_utils.h"
#include "chromeos/ash/components/dbus/attestation/attestation_ca.pb.h"
#include "chromeos/ash/components/dbus/constants/attestation_constants.h"
#include "chromeos/ash/components/install_attributes/install_attributes.h"

namespace enterprise_connectors {

namespace {

using ash::attestation::TpmChallengeKeyResultCode;

DTAttestationResult ToAttestationResult(TpmChallengeKeyResultCode code) {
  // Map the error codes as best as possible to the DTAttestationResult. The
  // `kFailedToGenerateResponse` will be considered the bucket of all unmappable
  // errors.
  switch (code) {
    case TpmChallengeKeyResultCode::kKeyRegistrationFailedError:
    case TpmChallengeKeyResultCode::kUserKeyNotAvailableError:
      return DTAttestationResult::kMissingSigningKey;
    case TpmChallengeKeyResultCode::kChallengeBadBase64Error:
      return DTAttestationResult::kBadChallengeFormat;
    default:
      return DTAttestationResult::kFailedToGenerateResponse;
  }
}

// Returns the VerifiedAccessFlow which should be used for attestation depending
// on the current device context.
::attestation::VerifiedAccessFlow GetVerifiedAccessFlow() {
  if (ash::InstallAttributes::Get()->IsEnterpriseManaged()) {
    return ::attestation::ENTERPRISE_MACHINE;
  }

  return ::attestation::DEVICE_TRUST_CONNECTOR;
}

AshAttestationServiceImpl::Username GetUserNameForKeyName(Profile* profile) {
  if (!profile) {
    return AshAttestationServiceImpl::Username();
  }

  return AshAttestationServiceImpl::Username(profile->GetProfileUserName());
}

// Returns the key name used for attestation. For ENTERPRISE_MACHINE the default
// key is used. Otherwise the key name is determined based on the username.
AshAttestationServiceImpl::KeyName GetKeyName(
    ::attestation::VerifiedAccessFlow flow_type,
    const AshAttestationServiceImpl::Username& username) {
  if (flow_type == ::attestation::ENTERPRISE_MACHINE) {
    return AshAttestationServiceImpl::KeyName();
  }
  return AshAttestationServiceImpl::GetDeviceTrustConnectorUserKeyName(
      username);
}

}  // namespace

AshAttestationServiceImpl::AshAttestationServiceImpl(Profile* profile)
    : profile_(profile) {}
AshAttestationServiceImpl::~AshAttestationServiceImpl() = default;

AshAttestationServiceImpl::KeyName
AshAttestationServiceImpl::GetDeviceTrustConnectorUserKeyName(
    const Username& username) {
  return KeyName(ash::attestation::kDeviceTrustConnectorKeyPrefix +
                 username.value());
}

base::WeakPtr<AshAttestationServiceImpl>
AshAttestationServiceImpl::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

void AshAttestationServiceImpl::TryPrepareKey() {
  ::attestation::VerifiedAccessFlow flow_type = GetVerifiedAccessFlow();

  if (flow_type == ::attestation::ENTERPRISE_MACHINE) {
    // Enterprise machine keys don't need to be prepared since they are uploaded
    // to DMServer after enrollment.
    return;
  }

  KeyName key_name =
      AshAttestationServiceImpl::GetDeviceTrustConnectorUserKeyName(
          GetUserNameForKeyName(profile_));

  auto tpm_key_challenge_subtle =
      ash::attestation::TpmChallengeKeySubtleFactory::Create();
  auto* tpm_key_challenge_subtle_ptr = tpm_key_challenge_subtle.get();
  tpm_key_challenge_subtle_ptr->StartPrepareKeyStep(
      flow_type, /*register_key=*/false,
      /*key_crypto_type=*/::attestation::KEY_TYPE_RSA,
      /*key_name_for_spkac=*/key_name.value(), profile_,
      base::BindOnce(&AshAttestationServiceImpl::KeyPrepareCallback,
                     weak_factory_.GetWeakPtr(),
                     std::move(tpm_key_challenge_subtle)),
      std::nullopt);
}

void AshAttestationServiceImpl::KeyPrepareCallback(
    std::unique_ptr<ash::attestation::TpmChallengeKeySubtle> tpm_key_challenger,
    const ash::attestation::TpmChallengeKeyResult& result) {
  if (!result.IsSuccess()) {
    LOG(ERROR) << "Key preparation failed with error: "
               << result.GetErrorMessage();
  }
}

void AshAttestationServiceImpl::BuildChallengeResponseForVAChallenge(
    const std::string& serialized_signed_challenge,
    base::Value::Dict signals,
    const std::set<DTCPolicyLevel>& levels,
    AttestationCallback callback) {
  std::string signals_json;
  if (!base::JSONWriter::Write(signals, &signals_json)) {
    std::move(callback).Run(
        {std::string(), DTAttestationResult::kFailedToSerializeSignals});
    return;
  }

  ::attestation::VerifiedAccessFlow flow_type = GetVerifiedAccessFlow();
  KeyName key_name = GetKeyName(flow_type, GetUserNameForKeyName(profile_));

  // Using flow type DEVICE_TRUST_CONNECTOR for attestation is currently only
  // supported inside a session.
  CHECK(flow_type != ::attestation::DEVICE_TRUST_CONNECTOR ||
        profile_ && profile_->IsRegularProfile());

  auto tpm_key_challenger =
      std::make_unique<ash::attestation::TpmChallengeKeyWithTimeout>();
  auto* tpm_key_challenger_ptr = tpm_key_challenger.get();
  tpm_key_challenger_ptr->BuildResponse(
      base::Seconds(15), flow_type, profile_,
      base::BindOnce(&AshAttestationServiceImpl::ReturnResult,
                     weak_factory_.GetWeakPtr(), std::move(tpm_key_challenger),
                     std::move(callback)),
      serialized_signed_challenge, /*register_key=*/false,
      /*key_crypto_type=*/::attestation::KEY_TYPE_RSA,
      /*key_name=*/key_name.value(),
      /*signals=*/signals_json);
}

void AshAttestationServiceImpl::ReturnResult(
    std::unique_ptr<ash::attestation::TpmChallengeKeyWithTimeout>
        tpm_key_challenger,
    AttestationCallback callback,
    const ash::attestation::TpmChallengeKeyResult& result) {
  std::string encoded_response;
  if (result.IsSuccess()) {
    encoded_response =
        ProtobufChallengeToJsonChallenge(result.challenge_response);
  } else {
    LOG(ERROR) << "Device Trust TPM error: " << result.GetErrorMessage();
  }
  std::move(callback).Run(
      {encoded_response, encoded_response.empty()
                             ? ToAttestationResult(result.result_code)
                             : DTAttestationResult::kSuccess});
}

}  // namespace enterprise_connectors