// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/auth/active_session_auth_controller_impl.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/test/ash_test_base.h"
#include "base/test/run_until.h"
#include "base/test/test_future.h"
#include "chromeos/ash/components/cryptohome/system_salt_getter.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_cryptohome_misc_client.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_userdataauth_client.h"
#include "chromeos/ash/components/login/auth/public/cryptohome_key_constants.h"
#include "chromeos/ash/components/osauth/impl/request/settings_auth_request.h"
#include "chromeos/ash/components/osauth/public/auth_parts.h"
#include "chromeos/ash/components/osauth/public/request/auth_request.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user_manager.h"
#include "third_party/cros_system_api/dbus/cryptohome/dbus-constants.h"
#include "ui/base/l10n/l10n_util.h"
namespace ash {
namespace {
constexpr char kUserEmail[] = "[email protected]";
constexpr char kExpectedPassword[] = "expected_password";
constexpr char kExpectedPin[] = "123456";
constexpr char kExpectedSalt[] = "test salt";
} // namespace
class ActiveSessionAuthControllerTest : public NoSessionAshTestBase {
public:
using OnAuthComplete =
base::test::TestFuture<bool, const ash::AuthProofToken&, base::TimeDelta>;
void SetUp() override {
InitializeUserManager();
AddUserToUserManager();
SystemSaltGetter::Initialize();
CryptohomeMiscClient::InitializeFake();
UserDataAuthClient::InitializeFake();
auth_parts_ = AuthParts::Create(&local_state_);
AshTestBase::SetUp();
GetSessionControllerClient()->DisableAutomaticallyProvideSigninPref();
GetSessionControllerClient()->Reset();
GetSessionControllerClient()->AddUserSession(
kUserEmail, user_manager::UserType::kRegular);
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
}
void TearDown() override {
Shell::Get()->session_controller()->ClearUserSessionsForTest();
auth_parts_.reset();
user_manager_->Destroy();
user_manager_.reset();
SystemSaltGetter::Shutdown();
CryptohomeMiscClient::Shutdown();
UserDataAuthClient::Shutdown();
AshTestBase::TearDown();
}
void InitializeUserManager() {
user_manager::UserManagerBase::RegisterPrefs(local_state_.registry());
user_manager_ =
std::make_unique<user_manager::FakeUserManager>(&local_state_);
user_manager_->Initialize();
}
void AddUserToUserManager() {
account_id_ = AccountId::FromUserEmail(kUserEmail);
const user_manager::User* user = user_manager_->AddUser(account_id_);
user_manager_->UserLoggedIn(account_id_, user->username_hash(), false,
false);
user_manager_->SetUserCryptohomeDataEphemeral(account_id_, false);
}
std::string HashPassword(const std::string& unhashed_password) {
Key key(std::move(unhashed_password));
key.Transform(Key::KEY_TYPE_SALTED_SHA256_TOP_HALF,
SystemSaltGetter::ConvertRawSaltToHexString(
FakeCryptohomeMiscClient::GetStubSystemSalt()));
return key.GetSecret();
}
std::string HashPin(const std::string& unhashed_pin) {
Key key(std::move(unhashed_pin));
key.Transform(Key::KEY_TYPE_SALTED_PBKDF2_AES256_1234, kExpectedSalt);
return key.GetSecret();
}
void AddGaiaPassword(const AccountId& user, const std::string& password) {
auto account_identifier =
cryptohome::CreateAccountIdentifierFromAccountId(user);
FakeUserDataAuthClient::TestApi::Get()->AddExistingUser(account_identifier);
// Hash the password, as only hashed passwords appear at the userdataauth
// level.
Key key(HashPassword(password));
user_data_auth::AuthFactor auth_factor;
user_data_auth::AuthInput auth_input;
auth_factor.set_label(ash::kCryptohomeGaiaKeyLabel);
auth_factor.set_type(user_data_auth::AUTH_FACTOR_TYPE_PASSWORD);
auth_input.mutable_password_input()->set_secret(key.GetSecret());
// Add the password key to the user.
FakeUserDataAuthClient::TestApi::Get()->AddAuthFactor(
account_identifier, auth_factor, auth_input);
}
void AddCryptohomePin(const AccountId& user, const std::string& pin) {
auto account_identifier =
cryptohome::CreateAccountIdentifierFromAccountId(user);
// Hash the pin, as only hashed secrets appear at the userdataauth
// level.
Key key(HashPin(pin));
// Add the pin key to the user.
user_data_auth::AuthFactor auth_factor;
user_data_auth::AuthInput auth_input;
auth_factor.set_label(ash::kCryptohomePinLabel);
auth_factor.set_type(user_data_auth::AUTH_FACTOR_TYPE_PIN);
auth_input.mutable_password_input()->set_secret(key.GetSecret());
FakeUserDataAuthClient::TestApi::Get()->AddAuthFactor(
account_identifier, auth_factor, auth_input);
}
protected:
AccountId account_id_;
TestingPrefServiceSimple local_state_;
std::unique_ptr<user_manager::FakeUserManager> user_manager_;
std::unique_ptr<AuthParts> auth_parts_;
};
// Tests that the StartAuthSession call to cryptohome includes the correct
// account id.
TEST_F(ActiveSessionAuthControllerTest,
StartAuthSessionCalledWithCorrectAccountIdAndReturnsPasswordFactor) {
AddGaiaPassword(account_id_, kExpectedPassword);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
std::make_unique<SettingsAuthRequest>(future.GetCallback()));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(
FakeUserDataAuthClient::Get()
->WasCalled<FakeUserDataAuthClient::Operation::kStartAuthSession>());
auto start_auth_session_request =
FakeUserDataAuthClient::Get()
->GetLastRequest<
FakeUserDataAuthClient::Operation::kStartAuthSession>();
EXPECT_EQ(start_auth_session_request.account_id().account_id(), kUserEmail);
AuthFactorSet available_factors =
ActiveSessionAuthControllerImpl::TestApi(controller)
.GetAvailableFactors();
EXPECT_EQ(1u, available_factors.size());
EXPECT_TRUE(available_factors.Has(AuthInputType::kPassword));
}
// Tests that the ListAuthFactors call to cryptohome includes the correct
// account id and returns the password and pin factors.
TEST_F(ActiveSessionAuthControllerTest, StartAuthSessionReturnsPasswordAndPin) {
AddGaiaPassword(account_id_, kExpectedPassword);
AddCryptohomePin(account_id_, kExpectedPin);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
std::make_unique<SettingsAuthRequest>(future.GetCallback()));
// Await show.
base::RunLoop().RunUntilIdle();
AuthFactorSet available_factors =
ActiveSessionAuthControllerImpl::TestApi(controller)
.GetAvailableFactors();
EXPECT_EQ(2u, available_factors.size());
EXPECT_TRUE(available_factors.Has(AuthInputType::kPassword));
EXPECT_TRUE(available_factors.Has(AuthInputType::kPin));
}
// Tests that the AuthenticateAuthFactor call to cryptohome includes the
// correct account id and password, and that the `OnAuthComplete` callback
// is called with correct parameters.
TEST_F(ActiveSessionAuthControllerTest, SubmitPassword) {
AddGaiaPassword(account_id_, kExpectedPassword);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
std::make_unique<SettingsAuthRequest>(future.GetCallback()));
// Await show.
base::RunLoop().RunUntilIdle();
ActiveSessionAuthControllerImpl::TestApi(controller)
.SubmitPassword(kExpectedPassword);
// Await authentication.
base::RunLoop().RunUntilIdle();
auto authenticate_auth_factor_request =
FakeUserDataAuthClient::Get()
->GetLastRequest<
FakeUserDataAuthClient::Operation::kAuthenticateAuthFactor>();
EXPECT_EQ(
authenticate_auth_factor_request.auth_input().password_input().secret(),
HashPassword(kExpectedPassword));
EXPECT_TRUE(future.IsReady());
EXPECT_EQ(future.Get<bool>(), true);
}
// Tests that the AuthenticateAuthFactor call to cryptohome includes the
// correct account id and password, and that the `OnAuthComplete` callback
// is not called with wrong credentials.
TEST_F(ActiveSessionAuthControllerTest, WrongPassword) {
AddGaiaPassword(account_id_, kExpectedPassword);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
std::make_unique<SettingsAuthRequest>(future.GetCallback()));
// Await show.
base::RunLoop().RunUntilIdle();
FakeUserDataAuthClient::Get()->SetNextOperationError(
FakeUserDataAuthClient::Operation::kAuthenticateAuthFactor,
cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
user_data_auth::CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED));
ActiveSessionAuthControllerImpl::TestApi(controller)
.SubmitPassword(kExpectedPassword);
// Await authentication.
base::RunLoop().RunUntilIdle();
auto authenticate_auth_factor_request =
FakeUserDataAuthClient::Get()
->GetLastRequest<
FakeUserDataAuthClient::Operation::kAuthenticateAuthFactor>();
EXPECT_EQ(
authenticate_auth_factor_request.auth_input().password_input().secret(),
HashPassword(kExpectedPassword));
EXPECT_FALSE(future.IsReady());
}
// Tests that the AuthenticateAuthFactor call to cryptohome includes the
// correct account id and pin, and that the `OnAuthComplete` callback
// is called with the correct credentials.
TEST_F(ActiveSessionAuthControllerTest, SubmitPin) {
AddGaiaPassword(account_id_, kExpectedPassword);
AddCryptohomePin(account_id_, kExpectedPin);
user_manager::KnownUser known_user(Shell::Get()->local_state());
known_user.SetStringPref(account_id_, prefs::kQuickUnlockPinSalt,
kExpectedSalt);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
std::make_unique<SettingsAuthRequest>(future.GetCallback()));
// Await show.
base::RunLoop().RunUntilIdle();
ActiveSessionAuthControllerImpl::TestApi(controller).SubmitPin(kExpectedPin);
// Await authentication.
base::RunLoop().RunUntilIdle();
auto authenticate_auth_factor_request =
FakeUserDataAuthClient::Get()
->GetLastRequest<
FakeUserDataAuthClient::Operation::kAuthenticateAuthFactor>();
EXPECT_EQ(authenticate_auth_factor_request.auth_input().pin_input().secret(),
HashPin(kExpectedPin));
EXPECT_TRUE(future.IsReady());
EXPECT_EQ(future.Get<bool>(), true);
}
// Tests that the AuthenticateAuthFactor call to cryptohome includes the
// account id and pin, and that the `OnAuthComplete` callback
// is not called with a wrong credentials error reply.
TEST_F(ActiveSessionAuthControllerTest, WrongPin) {
AddGaiaPassword(account_id_, kExpectedPassword);
AddCryptohomePin(account_id_, kExpectedPin);
user_manager::KnownUser known_user(Shell::Get()->local_state());
known_user.SetStringPref(account_id_, prefs::kQuickUnlockPinSalt,
kExpectedSalt);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
std::make_unique<SettingsAuthRequest>(future.GetCallback()));
// Await show.
base::RunLoop().RunUntilIdle();
FakeUserDataAuthClient::Get()->SetNextOperationError(
FakeUserDataAuthClient::Operation::kAuthenticateAuthFactor,
cryptohome::ErrorWrapper::CreateFromErrorCodeOnly(
user_data_auth::CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED));
ActiveSessionAuthControllerImpl::TestApi(controller).SubmitPin(kExpectedPin);
// Await authentication.
base::RunLoop().RunUntilIdle();
auto authenticate_auth_factor_request =
FakeUserDataAuthClient::Get()
->GetLastRequest<
FakeUserDataAuthClient::Operation::kAuthenticateAuthFactor>();
EXPECT_EQ(authenticate_auth_factor_request.auth_input().pin_input().secret(),
HashPin(kExpectedPin));
EXPECT_FALSE(future.IsReady());
}
// Tests that the AuthenticateAuthFactor calls to cryptohome are
// correctly formed when pin and password authentication are both
// tried.
TEST_F(ActiveSessionAuthControllerTest, BadPinThenGoodPassword) {
AddGaiaPassword(account_id_, kExpectedPassword);
AddCryptohomePin(account_id_, kExpectedPin);
const std::string bad_pin = "bad_pin";
user_manager::KnownUser known_user(Shell::Get()->local_state());
known_user.SetStringPref(account_id_, prefs::kQuickUnlockPinSalt,
kExpectedSalt);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
std::make_unique<SettingsAuthRequest>(future.GetCallback()));
// Await show.
base::RunLoop().RunUntilIdle();
// Await authentication with pin.
FakeUserDataAuthClient::TestApi::Get()->set_enable_auth_check(true);
ActiveSessionAuthControllerImpl::TestApi(controller).SubmitPin(bad_pin);
base::RunLoop().RunUntilIdle();
auto authenticate_auth_factor_request =
FakeUserDataAuthClient::Get()
->GetLastRequest<
FakeUserDataAuthClient::Operation::kAuthenticateAuthFactor>();
EXPECT_EQ(authenticate_auth_factor_request.auth_input().pin_input().secret(),
HashPin(bad_pin));
EXPECT_FALSE(future.IsReady());
// Await authentication with password.
ActiveSessionAuthControllerImpl::TestApi(controller)
.SubmitPassword(kExpectedPassword);
base::RunLoop().RunUntilIdle();
authenticate_auth_factor_request =
FakeUserDataAuthClient::Get()
->GetLastRequest<
FakeUserDataAuthClient::Operation::kAuthenticateAuthFactor>();
EXPECT_EQ(
authenticate_auth_factor_request.auth_input().password_input().secret(),
HashPassword(kExpectedPassword));
EXPECT_TRUE(future.IsReady());
EXPECT_EQ(future.Get<bool>(), true);
}
// Check the format and content of pin lockout status message.
TEST_F(ActiveSessionAuthControllerTest, PinLockoutMessage) {
AddGaiaPassword(account_id_, kExpectedPassword);
AddCryptohomePin(account_id_, kExpectedPin);
const std::string bad_pin = "bad_pin";
user_manager::KnownUser known_user(Shell::Get()->local_state());
known_user.SetStringPref(account_id_, prefs::kQuickUnlockPinSalt,
kExpectedSalt);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
auto test_api = ActiveSessionAuthControllerImpl::TestApi(controller);
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
std::make_unique<SettingsAuthRequest>(future.GetCallback()));
// Await show.
ASSERT_TRUE(base::test::RunUntil([&]() { return controller->IsShown(); }));
const base::TimeDelta in_a_while = base::Seconds(60);
cryptohome::PinStatus soft_lockout{in_a_while};
test_api.DisplayPinStatusMessage(soft_lockout);
EXPECT_EQ(
test_api.GetPinStatusMessage(),
l10n_util::GetStringFUTF16(IDS_ASH_IN_SESSION_AUTH_PIN_DELAY_REQUIRED,
u"1 minute, 0 seconds"));
cryptohome::PinStatus hard_lockout{base::TimeDelta::Max()};
test_api.DisplayPinStatusMessage(hard_lockout);
EXPECT_EQ(
test_api.GetPinStatusMessage(),
l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_PIN_TOO_MANY_ATTEMPTS));
}
// Tests that the OnAuthCancel callback is called with the correct
// parameters.
TEST_F(ActiveSessionAuthControllerTest, OnAuthCancel) {
AddGaiaPassword(account_id_, kExpectedPassword);
auto* controller = static_cast<ActiveSessionAuthControllerImpl*>(
Shell::Get()->active_session_auth_controller());
OnAuthComplete future;
Shell::Get()->active_session_auth_controller()->ShowAuthDialog(
std::make_unique<SettingsAuthRequest>(future.GetCallback()));
// Await show.
base::RunLoop().RunUntilIdle();
ActiveSessionAuthControllerImpl::TestApi(controller).Close();
// Await close.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(future.IsReady());
EXPECT_FALSE(future.Get<bool>());
EXPECT_EQ(future.Get<1>(), std::string{});
}
} // namespace ash