chromium/chrome/browser/ash/attestation/tpm_challenge_key_subtle_unittest.cc

// Copyright 2020 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/ash/attestation/tpm_challenge_key_subtle.h"

#include <stdint.h>

#include <optional>
#include <string>
#include <vector>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "base/time/time.h"
#include "chrome/browser/ash/attestation/mock_machine_certificate_uploader.h"
#include "chrome/browser/ash/attestation/tpm_challenge_key_result.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/platform_keys/key_permissions/fake_user_private_token_kpm_service.h"
#include "chrome/browser/ash/platform_keys/key_permissions/key_permissions_manager_impl.h"
#include "chrome/browser/ash/platform_keys/key_permissions/mock_key_permissions_manager.h"
#include "chrome/browser/ash/platform_keys/key_permissions/user_private_token_kpm_service_factory.h"
#include "chrome/browser/chromeos/platform_keys/platform_keys.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/attestation/mock_attestation_flow.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
#include "chromeos/ash/components/dbus/attestation/fake_attestation_client.h"
#include "chromeos/ash/components/dbus/attestation/interface.pb.h"
#include "chromeos/ash/components/dbus/constants/attestation_constants.h"
#include "chromeos/components/kiosk/kiosk_test_utils.h"
#include "chromeos/dbus/tpm_manager/tpm_manager_client.h"
#include "components/prefs/pref_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::test::RunOnceCallback;
using testing::_;
using testing::Invoke;
using testing::StrictMock;

namespace ash {
namespace attestation {
namespace {

constexpr char kTestUserEmail[] = "[email protected]";
constexpr char kTestUserGaiaId[] = "test_gaia_id";
constexpr char kEmptyKeyName[] = "";
constexpr char kNonDefaultKeyName[] = "key_name_123";
constexpr char kFakeCertificate[] = "fake_cert";
constexpr char kUnsupportedVAFlowTypeKey[] = "unsupported VA flow type";

std::string GetDefaultKeyName(::attestation::VerifiedAccessFlow type,
                              const std::string& username = std::string()) {
  switch (type) {
    case ::attestation::ENTERPRISE_MACHINE:
      return kEnterpriseMachineKey;
    case ::attestation::ENTERPRISE_USER:
      return kEnterpriseUserKey;
    default:
      return kUnsupportedVAFlowTypeKey;
  }
}

std::string GetChallenge() {
  constexpr uint8_t kBuffer[] = {0x0, 0x1, 0x2,  'c',  'h',
                                 'a', 'l', 0xfd, 0xfe, 0xff};
  return std::string(reinterpret_cast<const char*>(kBuffer), sizeof(kBuffer));
}

std::string GetChallengeResponse(bool include_spkac) {
  return AttestationClient::Get()
      ->GetTestInterface()
      ->GetEnterpriseChallengeFakeSignature(GetChallenge(), include_spkac);
}

std::string GetPublicKey() {
  constexpr uint8_t kBuffer[] = {0x0, 0x1, 0x2,  'p',  'u',
                                 'b', 'k', 0xfd, 0xfe, 0xff};
  return std::string(reinterpret_cast<const char*>(kBuffer), sizeof(kBuffer));
}

std::vector<uint8_t> GetPublicKeyBin() {
  constexpr uint8_t kBuffer[] = {0x0, 0x1, 0x2,  'p',  'u',
                                 'b', 'k', 0xfd, 0xfe, 0xff};
  return std::vector<uint8_t>(std::begin(kBuffer), std::end(kBuffer));
}

class CallbackObserver {
 public:
  TpmChallengeKeyCallback GetCallback() {
    return base::BindOnce(&CallbackObserver::Callback, base::Unretained(this));
  }

  bool IsResultReceived() const { return result_.has_value(); }

  const TpmChallengeKeyResult& GetResult() const {
    CHECK(result_.has_value()) << "Callback was never called";
    return result_.value();
  }

  void WaitForCallback() { loop_.Run(); }

 private:
  void Callback(const TpmChallengeKeyResult& result) {
    CHECK(!result_.has_value()) << "Callback was called more than once";
    result_ = result;
    loop_.Quit();
  }

  base::RunLoop loop_;
  std::optional<TpmChallengeKeyResult> result_;
};

template <typename T>
struct CallbackHolder {
  void SaveCallback(T new_callback) {
    CHECK(callback.is_null());
    callback = std::move(new_callback);
  }

  T callback;
};

//================= MockableFakeAttestationFlow ================================

class MockableFakeAttestationFlow : public MockAttestationFlow {
 public:
  MockableFakeAttestationFlow() {
    ON_CALL(*this, GetCertificate(_, _, _, _, _, _, _, _))
        .WillByDefault(
            Invoke(this, &MockableFakeAttestationFlow::GetCertificateInternal));
  }
  ~MockableFakeAttestationFlow() override = default;
  void set_status(AttestationStatus status) { status_ = status; }

 private:
  void GetCertificateInternal(
      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<AttestationFlow::CertProfileSpecificData>&
          profile_specific_data,
      CertificateCallback callback) {
    std::string certificate;
    if (status_ == ATTESTATION_SUCCESS) {
      certificate = certificate_;
      AttestationClient::Get()
          ->GetTestInterface()
          ->GetMutableKeyInfoReply(cryptohome::Identification(account_id).id(),
                                   key_name)
          ->set_public_key(public_key_);
    }
    std::move(callback).Run(status_, certificate);
  }
  AttestationStatus status_ = ATTESTATION_SUCCESS;
  const std::string certificate_ = kFakeCertificate;
  const std::string public_key_ = GetPublicKey();
};

//==================== TpmChallengeKeySubtleTestBase ===========================

// Describes which kind of profile will be used with TpmChallengeKeySubtle.
enum class TestProfileChoice {
  // Pass nullptr as Profile.
  kNoProfile,
  // Pass the sign-in Profile.
  kSigninProfile,
  // Pass the Profile of an unmanaged user.
  kUnmanagedProfile,
  // Pass the Profile of a managed, unaffiliated user.
  kUnaffiliatedProfile,
  // Pass the Profile of an affiliated user.
  kAffiliatedProfile,
  // Pass the Profile of signed-in kiosk user.
  kKioskProfile
};

class TpmChallengeKeySubtleTestBase : public ::testing::Test {
 public:
  explicit TpmChallengeKeySubtleTestBase(TestProfileChoice test_profile_choice);
  ~TpmChallengeKeySubtleTestBase() override;

 protected:
  // ::testing::Test:
  void SetUp() override;

  TestingProfile* CreateUserProfile(bool is_managed, bool is_affiliated);
  virtual TestingProfile* CreateKioskProfile();
  TestingProfile* GetProfile();
  ScopedCrosSettingsTestHelper* GetCrosSettingsHelper();
  StubInstallAttributes* GetInstallAttributes();
  std::string GetTestingProfileDomainName();

  // Runs StartPrepareKeyStep and checks that the result is equal to
  // |public_key|.
  void RunOneStepAndExpect(::attestation::VerifiedAccessFlow flow_type,
                           bool will_register_key,
                           const std::string& key_name,
                           const TpmChallengeKeyResult& public_key);
  // Runs StartPrepareKeyStep and checks that the result is success. Then runs
  // StartSignChallengeStep and checks that the result is equal to
  // |challenge_response|.
  void RunTwoStepsAndExpect(::attestation::VerifiedAccessFlow flow_type,
                            bool will_register_key,
                            const std::string& key_name,
                            const TpmChallengeKeyResult& challenge_response);
  // Runs first two steps and checks that results are success. Then runs
  // StartRegisterKeyStep and checks that the result is equal to
  // |register_result|.
  void RunThreeStepsAndExpect(::attestation::VerifiedAccessFlow flow_type,
                              bool will_register_key,
                              const std::string& key_name,
                              const TpmChallengeKeyResult& register_result);

  virtual ::attestation::KeyType KeyCryptoType();

 protected:
  const TestProfileChoice test_profile_choice_;

  content::BrowserTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};

  StrictMock<MockableFakeAttestationFlow> mock_attestation_flow_;
  std::unique_ptr<platform_keys::MockKeyPermissionsManager>
      system_token_key_permissions_manager_;
  std::unique_ptr<platform_keys::MockKeyPermissionsManager>
      user_private_token_key_permissions_manager_;
  MockMachineCertificateUploader mock_cert_uploader_;

  std::unique_ptr<TpmChallengeKeySubtle> challenge_key_subtle_;

  TestingProfileManager testing_profile_manager_;
  user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
      fake_user_manager_{std::make_unique<ash::FakeChromeUserManager>()};
  // A sign-in Profile is always created in SetUp().
  raw_ptr<TestingProfile> signin_profile_ = nullptr;
  // The profile that will be passed to TpmChallengeKeySubtle - can be nullptr.
  raw_ptr<TestingProfile> testing_profile_ = nullptr;
};

TpmChallengeKeySubtleTestBase::TpmChallengeKeySubtleTestBase(
    TestProfileChoice test_profile_choice)
    : test_profile_choice_(test_profile_choice),
      testing_profile_manager_(TestingBrowserProcess::GetGlobal()) {
  ::chromeos::TpmManagerClient::InitializeFake();
  AttestationClient::InitializeFake();
  CHECK(testing_profile_manager_.SetUp());

  challenge_key_subtle_ = std::make_unique<TpmChallengeKeySubtleImpl>(
      &mock_attestation_flow_, &mock_cert_uploader_);

  // By default make it reply that the certificate is already uploaded.
  ON_CALL(mock_cert_uploader_, WaitForUploadComplete)
      .WillByDefault(base::test::RunOnceCallbackRepeatedly<0>(true));
}

TpmChallengeKeySubtleTestBase::~TpmChallengeKeySubtleTestBase() {
  AttestationClient::Shutdown();
  ::chromeos::TpmManagerClient::Shutdown();
}

void TpmChallengeKeySubtleTestBase::SetUp() {
  signin_profile_ =
      testing_profile_manager_.CreateTestingProfile(chrome::kInitialProfile);
  switch (test_profile_choice_) {
    case TestProfileChoice::kNoProfile:
      testing_profile_ = nullptr;
      break;
    case TestProfileChoice::kSigninProfile:
      testing_profile_ = signin_profile_;
      break;
    case TestProfileChoice::kUnmanagedProfile:
      testing_profile_ =
          CreateUserProfile(/*is_managed=*/false, /*is_affiliated=*/false);
      break;
    case TestProfileChoice::kUnaffiliatedProfile:
      testing_profile_ =
          CreateUserProfile(/*is_managed=*/true, /*is_affiliated=*/false);
      break;
    case TestProfileChoice::kAffiliatedProfile:
      testing_profile_ =
          CreateUserProfile(/*is_managed=*/true, /*is_affiliated=*/true);
      break;
    case TestProfileChoice::kKioskProfile:
      testing_profile_ = CreateKioskProfile();
      break;
  }

  GetInstallAttributes()->SetCloudManaged(GetTestingProfileDomainName(),
                                          "device_id");

  system_token_key_permissions_manager_ =
      std::make_unique<platform_keys::MockKeyPermissionsManager>();
  platform_keys::KeyPermissionsManagerImpl::
      SetSystemTokenKeyPermissionsManagerForTesting(
          system_token_key_permissions_manager_.get());

  if (!testing_profile_) {
    return;
  }
  user_private_token_key_permissions_manager_ =
      std::make_unique<platform_keys::MockKeyPermissionsManager>();
  platform_keys::UserPrivateTokenKeyPermissionsManagerServiceFactory::
      GetInstance()
          ->SetTestingFactory(
              testing_profile_,
              base::BindRepeating(
                  &platform_keys::
                      BuildFakeUserPrivateTokenKeyPermissionsManagerService,
                  user_private_token_key_permissions_manager_.get()));
}

TestingProfile* TpmChallengeKeySubtleTestBase::CreateUserProfile(
    bool is_managed,
    bool is_affiliated) {
  TestingProfile* testing_profile =
      testing_profile_manager_.CreateTestingProfile(kTestUserEmail);
  CHECK(testing_profile);

  testing_profile->GetProfilePolicyConnector()->OverrideIsManagedForTesting(
      is_managed);

  auto test_account =
      AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId);
  fake_user_manager_->AddUserWithAffiliation(test_account, is_affiliated);

  return testing_profile;
}

TestingProfile* TpmChallengeKeySubtleTestBase::CreateKioskProfile() {
  return nullptr;
}

TestingProfile* TpmChallengeKeySubtleTestBase::GetProfile() {
  return testing_profile_;
}

ScopedCrosSettingsTestHelper*
TpmChallengeKeySubtleTestBase::GetCrosSettingsHelper() {
  return signin_profile_->ScopedCrosSettingsTestHelper();
}

StubInstallAttributes* TpmChallengeKeySubtleTestBase::GetInstallAttributes() {
  return GetCrosSettingsHelper()->InstallAttributes();
}

std::string TpmChallengeKeySubtleTestBase::GetTestingProfileDomainName() {
  if (!testing_profile_ || testing_profile_ == signin_profile_) {
    // Fallback to the constant domain if there is no `testing_profile_` or
    // a default `signin_profile_` only.
    return "fake.domain";
  }
  return gaia::ExtractDomainName(testing_profile_->GetProfileUserName());
}

::attestation::KeyType TpmChallengeKeySubtleTestBase::KeyCryptoType() {
  return ::attestation::KEY_TYPE_RSA;
}

void TpmChallengeKeySubtleTestBase::RunOneStepAndExpect(
    ::attestation::VerifiedAccessFlow flow_type,
    bool will_register_key,
    const std::string& key_name,
    const TpmChallengeKeyResult& public_key) {
  CallbackObserver callback_observer;
  challenge_key_subtle_->StartPrepareKeyStep(
      flow_type, will_register_key, KeyCryptoType(), key_name, GetProfile(),
      callback_observer.GetCallback(), /*signals=*/std::nullopt);
  callback_observer.WaitForCallback();

  EXPECT_EQ(callback_observer.GetResult(), public_key);
}

void TpmChallengeKeySubtleTestBase::RunTwoStepsAndExpect(
    ::attestation::VerifiedAccessFlow flow_type,
    bool will_register_key,
    const std::string& key_name,
    const TpmChallengeKeyResult& challenge_response) {
  RunOneStepAndExpect(flow_type, will_register_key, key_name,
                      TpmChallengeKeyResult::MakePublicKey(GetPublicKey()));

  CallbackObserver callback_observer;
  challenge_key_subtle_->StartSignChallengeStep(
      GetChallenge(), callback_observer.GetCallback());
  callback_observer.WaitForCallback();

  EXPECT_EQ(callback_observer.GetResult(), challenge_response);
}

void TpmChallengeKeySubtleTestBase::RunThreeStepsAndExpect(
    ::attestation::VerifiedAccessFlow flow_type,
    bool will_register_key,
    const std::string& key_name,
    const TpmChallengeKeyResult& register_result) {
  RunTwoStepsAndExpect(flow_type, will_register_key, key_name,
                       TpmChallengeKeyResult::MakeChallengeResponse(
                           GetChallengeResponse(will_register_key)));

  CallbackObserver callback_observer;
  challenge_key_subtle_->StartRegisterKeyStep(callback_observer.GetCallback());
  callback_observer.WaitForCallback();

  EXPECT_EQ(callback_observer.GetResult(), register_result);
}

//==============================================================================

// Tests all Profile* options where TpmChallengeKeySubtle can be used with
// device-wide keys, including passing profile=nullptr.
class DeviceKeysAccessTpmChallengeKeySubtleTest
    : public TpmChallengeKeySubtleTestBase,
      public ::testing::WithParamInterface<TestProfileChoice> {
 public:
  DeviceKeysAccessTpmChallengeKeySubtleTest()
      : TpmChallengeKeySubtleTestBase(GetParam()) {}
  ~DeviceKeysAccessTpmChallengeKeySubtleTest() = default;
};

INSTANTIATE_TEST_SUITE_P(
    DeviceKeysAccessTpmChallengeKeySubtleTest,
    DeviceKeysAccessTpmChallengeKeySubtleTest,
    testing::Values(TestProfileChoice::kNoProfile,
                    TestProfileChoice::kSigninProfile,
                    TestProfileChoice::kAffiliatedProfile));

// Tests TpmChallengeKeySubtle with the sign-in Profile only.
class SigninProfileTpmChallengeKeySubtleTest
    : public TpmChallengeKeySubtleTestBase {
 public:
  SigninProfileTpmChallengeKeySubtleTest()
      : TpmChallengeKeySubtleTestBase(TestProfileChoice::kSigninProfile) {}
  ~SigninProfileTpmChallengeKeySubtleTest() override = default;
};

// Tests TpmChallengeKeySubtle with an affiliated user profile only.
class AffiliatedUserTpmChallengeKeySubtleTest
    : public TpmChallengeKeySubtleTestBase {
 public:
  AffiliatedUserTpmChallengeKeySubtleTest()
      : TpmChallengeKeySubtleTestBase(TestProfileChoice::kAffiliatedProfile) {}
  ~AffiliatedUserTpmChallengeKeySubtleTest() override = default;
};

// Tests TpmChallengeKeySubtle with an unmanaged user profile only.
class UnmanagedUserTpmChallengeKeySubtleTest
    : public TpmChallengeKeySubtleTestBase {
 public:
  UnmanagedUserTpmChallengeKeySubtleTest()
      : TpmChallengeKeySubtleTestBase(TestProfileChoice::kUnmanagedProfile) {}
  ~UnmanagedUserTpmChallengeKeySubtleTest() override = default;
};

// Tests TpmChallengeKeySubtle with a managed, unaffiliated user profile only.
class UnaffiliatedUserTpmChallengeKeySubtleTest
    : public TpmChallengeKeySubtleTestBase {
 public:
  UnaffiliatedUserTpmChallengeKeySubtleTest()
      : TpmChallengeKeySubtleTestBase(TestProfileChoice::kUnaffiliatedProfile) {
  }
  ~UnaffiliatedUserTpmChallengeKeySubtleTest() override = default;
};

// Tests TpmChallengeKeySubtle in a kiosk session.
class KioskTpmChallengeKeySubtleTest : public TpmChallengeKeySubtleTestBase {
 public:
  KioskTpmChallengeKeySubtleTest()
      : TpmChallengeKeySubtleTestBase(TestProfileChoice::kKioskProfile) {}
  ~KioskTpmChallengeKeySubtleTest() override = default;

  TestingProfile* CreateKioskProfile() override {
    chromeos::SetUpFakeKioskSession();
    auto* user = fake_user_manager_->GetActiveUser();
    kiosk_user_email_ = user->GetAccountId().GetUserEmail();
    auto* testing_profile =
        testing_profile_manager_.CreateTestingProfile(kiosk_user_email_);
    testing_profile->GetProfilePolicyConnector()->OverrideIsManagedForTesting(
        true);
    fake_user_manager_->AddUserWithAffiliation(user->GetAccountId(),
                                               /* is_affiliated= */ true);
    return testing_profile;
  }

  const std::string& kiosk_user_email() const { return kiosk_user_email_; }

 private:
  std::string kiosk_user_email_;
};

TEST_P(DeviceKeysAccessTpmChallengeKeySubtleTest,
       UnsupportedVerifiedAccessFlow) {
  RunOneStepAndExpect(
      ::attestation::CBCM, /*will_register_key=*/false, kEmptyKeyName,
      TpmChallengeKeyResult::MakeError(
          TpmChallengeKeyResultCode::kVerifiedAccessFlowUnsupportedError));
}

TEST_P(DeviceKeysAccessTpmChallengeKeySubtleTest,
       DeviceKeyNonEnterpriseDevice) {
  GetInstallAttributes()->SetConsumerOwned();

  RunOneStepAndExpect(
      ::attestation::ENTERPRISE_MACHINE, /*will_register_key=*/false,
      kEmptyKeyName,
      TpmChallengeKeyResult::MakeError(
          TpmChallengeKeyResultCode::kNonEnterpriseDeviceError));
}

TEST_F(UnaffiliatedUserTpmChallengeKeySubtleTest, DeviceKeyUserNotManaged) {
  RunOneStepAndExpect(::attestation::ENTERPRISE_MACHINE,
                      /*will_register_key=*/false, kEmptyKeyName,
                      TpmChallengeKeyResult::MakeError(
                          TpmChallengeKeyResultCode::kUserNotManagedError));
}

TEST_F(SigninProfileTpmChallengeKeySubtleTest, UserKeyUserKeyNotAvailable) {
  RunOneStepAndExpect(
      ::attestation::ENTERPRISE_USER, /*will_register_key=*/false,
      kEmptyKeyName,
      TpmChallengeKeyResult::MakeError(
          TpmChallengeKeyResultCode::kUserKeyNotAvailableError));
}

// Checks that a user should be affiliated with a device
TEST_F(UnaffiliatedUserTpmChallengeKeySubtleTest, UserKeyUserNotAffiliated) {
  RunOneStepAndExpect(::attestation::ENTERPRISE_USER,
                      /*will_register_key=*/false, kEmptyKeyName,
                      TpmChallengeKeyResult::MakeError(
                          TpmChallengeKeyResultCode::kUserNotManagedError));
}

TEST_P(DeviceKeysAccessTpmChallengeKeySubtleTest, DoesKeyExistDbusFailed) {
  AttestationClient::Get()
      ->GetTestInterface()
      ->GetMutableKeyInfoReply(/*username=*/"", kEnterpriseMachineKey)
      ->set_status(::attestation::STATUS_DBUS_ERROR);

  RunOneStepAndExpect(
      ::attestation::ENTERPRISE_MACHINE, /*will_register_key=*/false,
      kEmptyKeyName,
      TpmChallengeKeyResult::MakeError(TpmChallengeKeyResultCode::kDbusError));
}

TEST_P(DeviceKeysAccessTpmChallengeKeySubtleTest, GetCertificateFailed) {
  const ::attestation::VerifiedAccessFlow flow_type =
      ::attestation::ENTERPRISE_MACHINE;

  mock_attestation_flow_.set_status(ATTESTATION_UNSPECIFIED_FAILURE);
  EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _, _, _, _));

  RunOneStepAndExpect(
      flow_type, /*will_register_key=*/false, kEmptyKeyName,
      TpmChallengeKeyResult::MakeError(
          TpmChallengeKeyResultCode::kGetCertificateFailedError));
}

TEST_P(DeviceKeysAccessTpmChallengeKeySubtleTest, KeyExists) {
  const ::attestation::VerifiedAccessFlow flow_type =
      ::attestation::ENTERPRISE_MACHINE;

  AttestationClient::Get()
      ->GetTestInterface()
      ->GetMutableKeyInfoReply(/*username=*/"", kEnterpriseMachineKey)
      ->set_public_key(GetPublicKey());

  RunOneStepAndExpect(flow_type, /*will_register_key=*/false, kEmptyKeyName,
                      TpmChallengeKeyResult::MakePublicKey(GetPublicKey()));
}

TEST_P(DeviceKeysAccessTpmChallengeKeySubtleTest, AttestationNotPrepared) {
  AttestationClient::Get()->GetTestInterface()->ConfigureEnrollmentPreparations(
      false);

  RunOneStepAndExpect(::attestation::ENTERPRISE_MACHINE,
                      /*will_register_key=*/false, kEmptyKeyName,
                      TpmChallengeKeyResult::MakeError(
                          TpmChallengeKeyResultCode::kResetRequiredError));
}

// Test that we get a proper error message in case we don't have a TPM.
TEST_P(DeviceKeysAccessTpmChallengeKeySubtleTest, AttestationUnsupported) {
  AttestationClient::Get()->GetTestInterface()->ConfigureEnrollmentPreparations(
      false);
  chromeos::TpmManagerClient::Get()
      ->GetTestInterface()
      ->mutable_nonsensitive_status_reply()
      ->set_is_enabled(false);

  RunOneStepAndExpect(
      ::attestation::ENTERPRISE_MACHINE, /*will_register_key=*/false,
      kEmptyKeyName,
      TpmChallengeKeyResult::MakeError(
          TpmChallengeKeyResultCode::kAttestationUnsupportedError));
}

TEST_P(DeviceKeysAccessTpmChallengeKeySubtleTest,
       AttestationPreparedDbusFailed) {
  AttestationClient::Get()
      ->GetTestInterface()
      ->ConfigureEnrollmentPreparationsStatus(::attestation::STATUS_DBUS_ERROR);

  RunOneStepAndExpect(
      ::attestation::ENTERPRISE_MACHINE, /*will_register_key=*/false,
      kEmptyKeyName,
      TpmChallengeKeyResult::MakeError(TpmChallengeKeyResultCode::kDbusError));
}

TEST_P(DeviceKeysAccessTpmChallengeKeySubtleTest,
       AttestationPreparedServiceInternalError) {
  AttestationClient::Get()
      ->GetTestInterface()
      ->ConfigureEnrollmentPreparationsStatus(
          ::attestation::STATUS_NOT_AVAILABLE);

  RunOneStepAndExpect(
      ::attestation::ENTERPRISE_MACHINE, /*will_register_key=*/false,
      kEmptyKeyName,
      TpmChallengeKeyResult::MakeError(
          TpmChallengeKeyResultCode::kAttestationServiceInternalError));
}

TEST_P(DeviceKeysAccessTpmChallengeKeySubtleTest,
       DeviceKeyNotRegisteredSuccess) {
  const ::attestation::VerifiedAccessFlow flow_type =
      ::attestation::ENTERPRISE_MACHINE;
  const std::string key_name = GetDefaultKeyName(flow_type);

  EXPECT_CALL(mock_attestation_flow_,
              GetCertificate(_, _, _, _, _, key_name, _, _));

  ::attestation::SignEnterpriseChallengeRequest expected_request;
  expected_request.set_key_label(key_name);
  expected_request.set_domain(std::string());
  expected_request.set_device_id(GetInstallAttributes()->GetDeviceId());
  expected_request.set_include_customer_id(true);
  expected_request.set_flow_type(::attestation::ENTERPRISE_MACHINE);
  expected_request.set_include_certificate(false);
  AttestationClient::Get()
      ->GetTestInterface()
      ->AllowlistSignEnterpriseChallengeKey(expected_request);

  RunTwoStepsAndExpect(flow_type, /*will_register_key=*/false, kEmptyKeyName,
                       TpmChallengeKeyResult::MakeChallengeResponse(
                           GetChallengeResponse(/*include_spkac=*/false)));
}

TEST_P(DeviceKeysAccessTpmChallengeKeySubtleTest, DeviceKeyRegisteredSuccess) {
  const ::attestation::VerifiedAccessFlow flow_type =
      ::attestation::ENTERPRISE_MACHINE;
  const char* const key_name = kNonDefaultKeyName;

  EXPECT_CALL(
      mock_attestation_flow_,
      GetCertificate(_, _, _, _, ::attestation::KEY_TYPE_RSA, key_name, _, _));

  ::attestation::SignEnterpriseChallengeRequest expected_request;
  expected_request.set_key_label(GetDefaultKeyName(flow_type));
  expected_request.set_key_name_for_spkac(key_name);
  expected_request.set_domain(std::string());
  expected_request.set_device_id(GetInstallAttributes()->GetDeviceId());
  expected_request.set_include_customer_id(true);
  expected_request.set_flow_type(::attestation::ENTERPRISE_MACHINE);
  expected_request.set_include_certificate(false);
  AttestationClient::Get()
      ->GetTestInterface()
      ->AllowlistSignEnterpriseChallengeKey(expected_request);

  AttestationClient::Get()->GetTestInterface()->AllowlistRegisterKey(
      /*username=*/"", key_name);

  EXPECT_CALL(
      *system_token_key_permissions_manager_,
      AllowKeyForUsage(/*callback=*/_, platform_keys::KeyUsage::kCorporate,
                       GetPublicKeyBin()))
      .WillOnce(RunOnceCallback<0>(chromeos::platform_keys::Status::kSuccess));

  RunThreeStepsAndExpect(flow_type, /*will_register_key=*/true, key_name,
                         TpmChallengeKeyResult::MakeSuccess());
}

class TpmChallengeKeySubtleTestECC : public TpmChallengeKeySubtleTestBase {
 public:
  TpmChallengeKeySubtleTestECC()
      : TpmChallengeKeySubtleTestBase(TestProfileChoice::kAffiliatedProfile) {}
  ~TpmChallengeKeySubtleTestECC() override = default;

  // TpmChallengeKeySubtleTestBase
  ::attestation::KeyType KeyCryptoType() override {
    return ::attestation::KEY_TYPE_ECC;
  }
};

TEST_F(TpmChallengeKeySubtleTestECC, DeviceKeyRegisteredSuccessECC) {
  const ::attestation::VerifiedAccessFlow flow_type =
      ::attestation::ENTERPRISE_MACHINE;
  const char* const key_name = kNonDefaultKeyName;

  EXPECT_CALL(
      mock_attestation_flow_,
      GetCertificate(_, _, _, _, ::attestation::KEY_TYPE_ECC, key_name, _, _));

  RunOneStepAndExpect(flow_type, /*will_register_key=*/true, key_name,
                      TpmChallengeKeyResult::MakePublicKey(GetPublicKey()));
}

TEST_F(AffiliatedUserTpmChallengeKeySubtleTest,
       UserKeyRegisteredSuccessDefaultNameRsa) {
  const ::attestation::VerifiedAccessFlow flow_type =
      ::attestation::ENTERPRISE_USER;
  const std::string key_name = std::string(kEnterpriseUserKey);

  EXPECT_CALL(
      mock_attestation_flow_,
      GetCertificate(_, _, _, _, ::attestation::KEY_TYPE_RSA, key_name, _, _));

  RunOneStepAndExpect(flow_type, /*will_register_key=*/true, "",
                      TpmChallengeKeyResult::MakePublicKey(GetPublicKey()));
}

TEST_F(TpmChallengeKeySubtleTestECC, UserKeyRegisteredSuccessDefaultNameECC) {
  const ::attestation::VerifiedAccessFlow flow_type =
      ::attestation::ENTERPRISE_USER;
  const std::string key_name = std::string(kEnterpriseUserKey) + "-ecdsa";

  EXPECT_CALL(
      mock_attestation_flow_,
      GetCertificate(_, _, _, _, ::attestation::KEY_TYPE_ECC, key_name, _, _));

  RunOneStepAndExpect(flow_type, /*will_register_key=*/true, "",
                      TpmChallengeKeyResult::MakePublicKey(GetPublicKey()));
}

TEST_F(AffiliatedUserTpmChallengeKeySubtleTest, UserKeyNotRegisteredSuccess) {
  const ::attestation::VerifiedAccessFlow flow_type =
      ::attestation::ENTERPRISE_USER;
  const std::string key_name = GetDefaultKeyName(flow_type);

  EXPECT_CALL(mock_attestation_flow_,
              GetCertificate(_, _, _, _, _, key_name, _, _));

  ::attestation::SignEnterpriseChallengeRequest expected_request;
  expected_request.set_username(kTestUserEmail);
  expected_request.set_key_label(GetDefaultKeyName(flow_type));
  expected_request.set_domain(kTestUserEmail);
  expected_request.set_device_id(GetInstallAttributes()->GetDeviceId());
  expected_request.set_include_customer_id(false);
  expected_request.set_flow_type(::attestation::ENTERPRISE_USER);
  expected_request.set_include_certificate(true);
  AttestationClient::Get()
      ->GetTestInterface()
      ->AllowlistSignEnterpriseChallengeKey(expected_request);

  RunTwoStepsAndExpect(flow_type, /*will_register_key=*/false, kEmptyKeyName,
                       TpmChallengeKeyResult::MakeChallengeResponse(
                           GetChallengeResponse(/*include_spkac=*/false)));
}

TEST_F(AffiliatedUserTpmChallengeKeySubtleTest, UserKeyRegisteredSuccess) {
  const ::attestation::VerifiedAccessFlow flow_type =
      ::attestation::ENTERPRISE_USER;
  const char* const key_name = kNonDefaultKeyName;

  EXPECT_CALL(mock_attestation_flow_,
              GetCertificate(_, _, _, _, _, key_name, _, _));

  ::attestation::SignEnterpriseChallengeRequest expected_request;
  expected_request.set_username(kTestUserEmail);
  expected_request.set_key_label(kNonDefaultKeyName);
  expected_request.set_domain(kTestUserEmail);
  expected_request.set_device_id(GetInstallAttributes()->GetDeviceId());
  expected_request.set_include_customer_id(false);
  expected_request.set_flow_type(::attestation::ENTERPRISE_USER);
  expected_request.set_include_certificate(true);
  AttestationClient::Get()
      ->GetTestInterface()
      ->AllowlistSignEnterpriseChallengeKey(expected_request);

  AttestationClient::Get()->GetTestInterface()->AllowlistRegisterKey(
      kTestUserEmail, key_name);

  EXPECT_CALL(
      *user_private_token_key_permissions_manager_,
      AllowKeyForUsage(/*callback=*/_, platform_keys::KeyUsage::kCorporate,
                       GetPublicKeyBin()))
      .WillOnce(RunOnceCallback<0>(chromeos::platform_keys::Status::kSuccess));

  RunThreeStepsAndExpect(flow_type, /*will_register_key=*/true, key_name,
                         TpmChallengeKeyResult::MakeSuccess());
}

TEST_P(DeviceKeysAccessTpmChallengeKeySubtleTest, SignChallengeFailed) {
  const ::attestation::VerifiedAccessFlow flow_type =
      ::attestation::ENTERPRISE_MACHINE;

  EXPECT_CALL(
      mock_attestation_flow_,
      GetCertificate(_, _, _, _, _, GetDefaultKeyName(flow_type), _, _));

  // The signing operations fails because we don't allowlist any key.
  RunTwoStepsAndExpect(
      flow_type, /*will_register_key=*/false, kEmptyKeyName,
      TpmChallengeKeyResult::MakeError(
          TpmChallengeKeyResultCode::kSignChallengeFailedError));
}

TEST_F(AffiliatedUserTpmChallengeKeySubtleTest, RestorePreparedKeyState) {
  const ::attestation::VerifiedAccessFlow flow_type =
      ::attestation::ENTERPRISE_USER;
  const char* const key_name = kNonDefaultKeyName;

  // Inject mocks into the next result of CreateForPreparedKey.
  TpmChallengeKeySubtleFactory::SetForTesting(
      std::make_unique<TpmChallengeKeySubtleImpl>(&mock_attestation_flow_,
                                                  &mock_cert_uploader_));

  challenge_key_subtle_ = TpmChallengeKeySubtleFactory::CreateForPreparedKey(
      flow_type, /*will_register_key=*/true, ::attestation::KEY_TYPE_RSA,
      key_name, GetPublicKey(), GetProfile());

  ::attestation::SignEnterpriseChallengeRequest expected_request;
  expected_request.set_username(kTestUserEmail);
  expected_request.set_key_label(kNonDefaultKeyName);
  expected_request.set_domain(kTestUserEmail);
  expected_request.set_device_id(GetInstallAttributes()->GetDeviceId());
  expected_request.set_include_customer_id(false);
  expected_request.set_flow_type(::attestation::ENTERPRISE_USER);
  expected_request.set_include_certificate(true);
  AttestationClient::Get()
      ->GetTestInterface()
      ->AllowlistSignEnterpriseChallengeKey(expected_request);

  {
    CallbackObserver callback_observer;
    challenge_key_subtle_->StartSignChallengeStep(
        GetChallenge(), callback_observer.GetCallback());
    callback_observer.WaitForCallback();

    EXPECT_EQ(callback_observer.GetResult(),
              TpmChallengeKeyResult::MakeChallengeResponse(
                  GetChallengeResponse(/*include_spkac=*/true)));
  }

  AttestationClient::Get()->GetTestInterface()->AllowlistRegisterKey(
      kTestUserEmail, key_name);

  EXPECT_CALL(
      *user_private_token_key_permissions_manager_,
      AllowKeyForUsage(/*callback=*/_, platform_keys::KeyUsage::kCorporate,
                       GetPublicKeyBin()))
      .WillOnce(RunOnceCallback<0>(chromeos::platform_keys::Status::kSuccess));

  {
    CallbackObserver callback_observer;
    challenge_key_subtle_->StartRegisterKeyStep(
        callback_observer.GetCallback());
    callback_observer.WaitForCallback();

    EXPECT_EQ(callback_observer.GetResult(),
              TpmChallengeKeyResult::MakeSuccess());
  }
}

TEST_F(AffiliatedUserTpmChallengeKeySubtleTest, KeyRegistrationFailed) {
  const ::attestation::VerifiedAccessFlow flow_type =
      ::attestation::ENTERPRISE_USER;
  const char* const key_name = kNonDefaultKeyName;

  // Inject mocks into the next result of CreateForPreparedKey.
  TpmChallengeKeySubtleFactory::SetForTesting(
      std::make_unique<TpmChallengeKeySubtleImpl>(&mock_attestation_flow_,
                                                  &mock_cert_uploader_));

  challenge_key_subtle_ = TpmChallengeKeySubtleFactory::CreateForPreparedKey(
      flow_type, /*will_register_key=*/true, ::attestation::KEY_TYPE_RSA,
      key_name, GetPublicKey(), GetProfile());

  CallbackObserver callback_observer;
  challenge_key_subtle_->StartRegisterKeyStep(callback_observer.GetCallback());
  callback_observer.WaitForCallback();

  EXPECT_EQ(callback_observer.GetResult(),
            TpmChallengeKeyResult::MakeError(
                TpmChallengeKeyResultCode::kKeyRegistrationFailedError));
}

TEST_F(AffiliatedUserTpmChallengeKeySubtleTest, GetPublicKeyFailed) {
  const char* const key_name = kNonDefaultKeyName;

  EXPECT_CALL(mock_attestation_flow_,
              GetCertificate(_, _, _, _, _, key_name, _, _));

  // Force the attestation client to report absence even after successful
  // attestation flow.
  AttestationClient::Get()
      ->GetTestInterface()
      ->GetMutableKeyInfoReply(/*username=*/"", key_name)
      ->set_status(::attestation::STATUS_INVALID_PARAMETER);

  RunOneStepAndExpect(::attestation::ENTERPRISE_MACHINE,
                      /*will_register_key=*/true, key_name,
                      TpmChallengeKeyResult::MakeError(
                          TpmChallengeKeyResultCode::kGetPublicKeyFailedError));
}

TEST_F(AffiliatedUserTpmChallengeKeySubtleTest, WaitForCertificateUploaded) {
  const char* const key_name = kNonDefaultKeyName;

  using CallbackHolderT =
      CallbackHolder<MachineCertificateUploader::UploadCallback>;
  CallbackHolderT callback_holder;
  EXPECT_CALL(mock_cert_uploader_, WaitForUploadComplete)
      .WillOnce(
          testing::Invoke(&callback_holder, &CallbackHolderT::SaveCallback));

  EXPECT_CALL(mock_attestation_flow_,
              GetCertificate(_, _, _, _, _, key_name, _, _));

  CallbackObserver callback_observer;
  challenge_key_subtle_->StartPrepareKeyStep(
      ::attestation::ENTERPRISE_MACHINE, /*will_register_key=*/true,
      ::attestation::KEY_TYPE_RSA, key_name, GetProfile(),
      callback_observer.GetCallback(),
      /*signals=*/std::nullopt);

  // |challenge_key_subtle_| should wait until the certificate is uploaded.
  task_environment_.FastForwardBy(base::Minutes(10));
  EXPECT_FALSE(callback_observer.IsResultReceived());

  // Emulate callback from the certificate uploader, |challenge_key_subtle_|
  // should be able to continue now.
  std::move(callback_holder.callback).Run(true);
  callback_observer.WaitForCallback();

  EXPECT_EQ(callback_observer.GetResult(),
            TpmChallengeKeyResult::MakePublicKey(GetPublicKey()));
}

// Check that the class works when MachineCertificateUploader is not provided
// (e.g. if device is managed by Active Directory).
TEST_F(AffiliatedUserTpmChallengeKeySubtleTest, NoCertificateUploaderSuccess) {
  const char* const key_name = kNonDefaultKeyName;

  challenge_key_subtle_ = std::make_unique<TpmChallengeKeySubtleImpl>(
      &mock_attestation_flow_, /*machine_certificate_uploader=*/nullptr);

  EXPECT_CALL(mock_attestation_flow_,
              GetCertificate(_, _, _, _, _, key_name, _, _));

  RunOneStepAndExpect(::attestation::ENTERPRISE_USER,
                      /*will_register_key=*/true, key_name,
                      TpmChallengeKeyResult::MakePublicKey(GetPublicKey()));
}

// Checks that the include_customer_id field is true in kiosk sessions.
TEST_F(KioskTpmChallengeKeySubtleTest, IncludesCustomerId) {
  const ::attestation::VerifiedAccessFlow flow_type =
      ::attestation::ENTERPRISE_USER;
  const std::string key_name = GetDefaultKeyName(flow_type);

  EXPECT_CALL(mock_attestation_flow_,
              GetCertificate(_, _, _, _, _, key_name, _, _));

  ::attestation::SignEnterpriseChallengeRequest expected_request;
  expected_request.set_username(kiosk_user_email());
  expected_request.set_key_label(GetDefaultKeyName(flow_type));
  expected_request.set_domain(kiosk_user_email());
  expected_request.set_device_id(GetInstallAttributes()->GetDeviceId());
  expected_request.set_include_customer_id(true);
  expected_request.set_flow_type(::attestation::ENTERPRISE_USER);
  expected_request.set_include_certificate(true);
  AttestationClient::Get()
      ->GetTestInterface()
      ->AllowlistSignEnterpriseChallengeKey(expected_request);

  RunTwoStepsAndExpect(flow_type, /*will_register_key=*/false, kEmptyKeyName,
                       TpmChallengeKeyResult::MakeChallengeResponse(
                           GetChallengeResponse(/*include_spkac=*/false)));
}

// TODO(b/277707201): remove once user email from login screen can be passed to
// TpmChallengeKeySubtle.
TEST_F(SigninProfileTpmChallengeKeySubtleTest,
       DeviceTrustConnectorProfileRequired) {
  RunOneStepAndExpect(
      ::attestation::DEVICE_TRUST_CONNECTOR, /*will_register_key=*/false,
      kNonDefaultKeyName,
      TpmChallengeKeyResult::MakeError(
          TpmChallengeKeyResultCode::kUserKeyNotAvailableError));
}

TEST_F(UnmanagedUserTpmChallengeKeySubtleTest,
       DeviceTrustConnectorUserNotManaged) {
  RunOneStepAndExpect(::attestation::DEVICE_TRUST_CONNECTOR,
                      /*will_register_key=*/false, kNonDefaultKeyName,
                      TpmChallengeKeyResult::MakeError(
                          TpmChallengeKeyResultCode::kUserNotManagedError));
}

TEST_F(UnaffiliatedUserTpmChallengeKeySubtleTest, DeviceTrustConnectorSuccess) {
  const ::attestation::VerifiedAccessFlow flow_type =
      ::attestation::DEVICE_TRUST_CONNECTOR;
  EXPECT_CALL(mock_attestation_flow_,
              GetCertificate(_, _, _, _, _, kNonDefaultKeyName, _, _));

  ::attestation::SignEnterpriseChallengeRequest expected_request;
  expected_request.set_key_label(kNonDefaultKeyName);
  expected_request.set_domain(kTestUserEmail);
  expected_request.set_device_id(GetInstallAttributes()->GetDeviceId());
  expected_request.set_include_customer_id(false);
  expected_request.set_flow_type(::attestation::DEVICE_TRUST_CONNECTOR);
  expected_request.set_include_certificate(true);
  AttestationClient::Get()
      ->GetTestInterface()
      ->AllowlistSignEnterpriseChallengeKey(expected_request);

  RunTwoStepsAndExpect(flow_type, /*will_register_key=*/false,
                       kNonDefaultKeyName,
                       TpmChallengeKeyResult::MakeChallengeResponse(
                           GetChallengeResponse(/*include_spkac=*/false)));
}

}  // namespace
}  // namespace attestation
}  // namespace ash