| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <cstddef> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| |
| #include <base/files/file_path.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/functional/bind.h> |
| #include <base/memory/scoped_refptr.h> |
| #include <base/test/bind.h> |
| #include <base/test/mock_callback.h> |
| #include <base/test/task_environment.h> |
| #include <base/test/test_future.h> |
| #include <brillo/cryptohome.h> |
| #include <brillo/secure_blob.h> |
| #include <cryptohome/proto_bindings/auth_factor.pb.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <libhwsec/factory/tpm2_simulator_factory_for_test.h> |
| #include <libhwsec/factory/factory_impl.h> |
| #include <libhwsec/frontend/cryptohome/mock_frontend.h> |
| #include <libhwsec/frontend/pinweaver/mock_frontend.h> |
| #include <libhwsec-foundation/error/testing_helper.h> |
| #include <libhwsec-foundation/status/status_chain.h> |
| #include <cryptohome/proto_bindings/UserDataAuth.pb.h> |
| |
| #include "cryptohome/auth_blocks/auth_block.h" |
| #include "cryptohome/auth_blocks/auth_block_type.h" |
| #include "cryptohome/auth_blocks/auth_block_utility_impl.h" |
| #include "cryptohome/auth_blocks/fp_service.h" |
| #include "cryptohome/auth_blocks/mock_auth_block_utility.h" |
| #include "cryptohome/auth_blocks/scrypt_auth_block.h" |
| #include "cryptohome/auth_factor/auth_factor_manager.h" |
| #include "cryptohome/auth_factor/auth_factor_storage_type.h" |
| #include "cryptohome/auth_factor/flatbuffer.h" |
| #include "cryptohome/auth_factor/types/manager.h" |
| #include "cryptohome/auth_input_utils.h" |
| #include "cryptohome/auth_session_manager.h" |
| #include "cryptohome/challenge_credentials/mock_challenge_credentials_helper.h" |
| #include "cryptohome/cleanup/mock_user_oldest_activity_timestamp_manager.h" |
| #include "cryptohome/crypto.h" |
| #include "cryptohome/fake_features.h" |
| #include "cryptohome/features.h" |
| #include "cryptohome/filesystem_layout.h" |
| #include "cryptohome/flatbuffer_schemas/auth_block_state.h" |
| #include "cryptohome/key_objects.h" |
| #include "cryptohome/mock_cryptohome_keys_manager.h" |
| #include "cryptohome/mock_install_attributes.h" |
| #include "cryptohome/mock_key_challenge_service_factory.h" |
| #include "cryptohome/mock_keyset_management.h" |
| #include "cryptohome/mock_platform.h" |
| #include "cryptohome/mock_vault_keyset_factory.h" |
| #include "cryptohome/pinweaver_manager/le_credential_manager_impl.h" |
| #include "cryptohome/pinweaver_manager/mock_le_credential_manager.h" |
| #include "cryptohome/pkcs11/mock_pkcs11_token_factory.h" |
| #include "cryptohome/storage/mock_homedirs.h" |
| #include "cryptohome/user_secret_stash/decrypted.h" |
| #include "cryptohome/user_secret_stash/storage.h" |
| #include "cryptohome/user_secret_stash/user_secret_stash.h" |
| #include "cryptohome/user_session/mock_user_session_factory.h" |
| #include "cryptohome/user_session/user_session_map.h" |
| #include "cryptohome/userdataauth.h" |
| #include "cryptohome/vault_keyset.h" |
| #include "cryptohome/vault_keyset_factory.h" |
| |
| namespace cryptohome { |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::Contains; |
| using ::testing::Eq; |
| using ::testing::IsEmpty; |
| using ::testing::NiceMock; |
| using ::testing::NotNull; |
| using ::testing::Optional; |
| using ::testing::Return; |
| using ::testing::UnorderedElementsAre; |
| using ::testing::VariantWith; |
| |
| using base::test::TaskEnvironment; |
| using base::test::TestFuture; |
| using brillo::cryptohome::home::SanitizeUserName; |
| using cryptohome::enumeration::SerializedAuthFactorType; |
| using cryptohome::error::CryptohomeError; |
| using hwsec_foundation::error::testing::IsOk; |
| using hwsec_foundation::error::testing::NotOk; |
| using hwsec_foundation::error::testing::ReturnValue; |
| using hwsec_foundation::status::OkStatus; |
| |
| using AuthenticateTestFuture = |
| TestFuture<const AuthSession::PostAuthAction&, CryptohomeStatus>; |
| |
| constexpr char kUsername[] = "foo@example.com"; |
| constexpr char kPassword[] = "password"; |
| constexpr char kPin[] = "1234"; |
| constexpr char kWrongPin[] = "4321"; |
| constexpr char kPasswordLabel[] = "password_label"; |
| constexpr char kPassword2[] = "password2"; |
| constexpr char kPasswordLabel2[] = "password_label2"; |
| constexpr char kDefaultLabel[] = "legacy-0"; |
| constexpr char kPinLabel[] = "pin_label"; |
| constexpr char kSalt[] = "salt"; |
| constexpr char kPublicHash[] = "public key hash"; |
| constexpr int kAuthValueRounds = 5; |
| |
| const brillo::SecureBlob kInitialBlob64(64, 'A'); |
| const brillo::SecureBlob kInitialBlob32(32, 'A'); |
| const brillo::SecureBlob kAdditionalBlob32(32, 'B'); |
| const brillo::SecureBlob kInitialBlob16(16, 'C'); |
| const brillo::SecureBlob kAdditionalBlob16(16, 'D'); |
| |
| // Helper function to create a mock vault keyset with some useful default |
| // functions to create basic minimal VKs. |
| std::unique_ptr<VaultKeysetFactory> CreateMockVaultKeysetFactory() { |
| auto factory = std::make_unique<MockVaultKeysetFactory>(); |
| ON_CALL(*factory, New(_, _)) |
| .WillByDefault([](Platform* platform, Crypto* crypto) { |
| auto* vk = new VaultKeyset(); |
| vk->Initialize(platform, crypto); |
| return vk; |
| }); |
| ON_CALL(*factory, NewBackup(_, _)) |
| .WillByDefault([](Platform* platform, Crypto* crypto) { |
| auto* vk = new VaultKeyset(); |
| vk->InitializeAsBackup(platform, crypto); |
| return vk; |
| }); |
| return factory; |
| } |
| |
| AuthSession::AuthenticateAuthFactorRequest ToAuthenticateRequest( |
| std::vector<std::string> labels, user_data_auth::AuthInput auth_input) { |
| return AuthSession::AuthenticateAuthFactorRequest{ |
| .auth_factor_labels = std::move(labels), |
| .auth_input_proto = std::move(auth_input), |
| .flags = {.force_full_auth = AuthSession::ForceFullAuthFlag::kNone}, |
| }; |
| } |
| |
| } // namespace |
| |
| class AuthSessionTestWithKeysetManagement : public ::testing::Test { |
| public: |
| AuthSessionTestWithKeysetManagement() { |
| // Setting HWSec Expectations. |
| EXPECT_CALL(hwsec_, IsEnabled()).WillRepeatedly(ReturnValue(true)); |
| EXPECT_CALL(hwsec_, IsReady()).WillRepeatedly(ReturnValue(true)); |
| EXPECT_CALL(hwsec_, IsSealingSupported()).WillRepeatedly(ReturnValue(true)); |
| EXPECT_CALL(hwsec_, GetManufacturer()) |
| .WillRepeatedly(ReturnValue(0x43524f53)); |
| EXPECT_CALL(hwsec_, GetAuthValue(_, _)) |
| .WillRepeatedly(ReturnValue(brillo::SecureBlob())); |
| EXPECT_CALL(hwsec_, SealWithCurrentUser(_, _, _)) |
| .WillRepeatedly(ReturnValue(brillo::Blob())); |
| EXPECT_CALL(hwsec_, GetPubkeyHash(_)) |
| .WillRepeatedly(ReturnValue(brillo::Blob())); |
| |
| crypto_.set_le_manager_for_testing( |
| std::make_unique<MockLECredentialManager>()); |
| crypto_.Init(); |
| |
| auth_session_manager_ = |
| std::make_unique<AuthSessionManager>(AuthSession::BackingApis{ |
| &crypto_, &platform_, &user_session_map_, &keyset_management_, |
| &auth_block_utility_, &auth_factor_driver_manager_, |
| &auth_factor_manager_, &uss_storage_, &user_metadata_reader_, |
| &features_.async}); |
| // Initializing UserData class. |
| userdataauth_.set_platform(&platform_); |
| userdataauth_.set_homedirs(&homedirs_); |
| userdataauth_.set_user_session_factory(&user_session_factory_); |
| userdataauth_.set_keyset_management(&keyset_management_); |
| userdataauth_.set_auth_factor_driver_manager_for_testing( |
| &auth_factor_driver_manager_); |
| userdataauth_.set_auth_factor_manager_for_testing(&auth_factor_manager_); |
| userdataauth_.set_uss_storage_for_testing(&uss_storage_); |
| userdataauth_.set_auth_session_manager(auth_session_manager_.get()); |
| userdataauth_.set_pkcs11_token_factory(&pkcs11_token_factory_); |
| userdataauth_.set_user_activity_timestamp_manager( |
| &user_activity_timestamp_manager_); |
| userdataauth_.set_install_attrs(&install_attrs_); |
| userdataauth_.set_mount_task_runner( |
| task_environment_.GetMainThreadTaskRunner()); |
| userdataauth_.set_auth_block_utility(&auth_block_utility_); |
| userdataauth_.set_features(&features_.object); |
| file_system_keyset_ = FileSystemKeyset::CreateRandom(); |
| AddUser(kUsername, kPassword); |
| PrepareDirectoryStructure(); |
| } |
| |
| protected: |
| struct UserInfo { |
| Username username; |
| ObfuscatedUsername obfuscated; |
| brillo::SecureBlob passkey; |
| base::FilePath homedir_path; |
| base::FilePath user_path; |
| }; |
| |
| void AddUser(const std::string& name, const std::string& password) { |
| Username username(name); |
| ObfuscatedUsername obfuscated = SanitizeUserName(username); |
| brillo::SecureBlob passkey(password); |
| |
| UserInfo info = {username, obfuscated, passkey, UserPath(obfuscated), |
| brillo::cryptohome::home::GetHashedUserPath(obfuscated)}; |
| users_.push_back(info); |
| } |
| |
| void PrepareDirectoryStructure() { |
| ASSERT_TRUE(platform_.CreateDirectory(ShadowRoot())); |
| ASSERT_TRUE(platform_.CreateDirectory( |
| brillo::cryptohome::home::GetUserPathPrefix())); |
| // We only need the homedir path, not the vault/mount paths. |
| for (const auto& user : users_) { |
| ASSERT_TRUE(platform_.CreateDirectory(user.homedir_path)); |
| } |
| } |
| |
| // Configures the mock Hwsec to simulate correct replies for authentication |
| // (unsealing) requests. |
| void SetUpHwsecAuthenticationMocks() { |
| // When sealing, remember the secret and configure the unseal mock to return |
| // it. |
| EXPECT_CALL(hwsec_, SealWithCurrentUser(_, _, _)) |
| .WillRepeatedly([this](auto, auto, auto unsealed_value) { |
| EXPECT_CALL(hwsec_, UnsealWithCurrentUser(_, _, _)) |
| .WillRepeatedly(ReturnValue(unsealed_value)); |
| return brillo::Blob(); |
| }); |
| EXPECT_CALL(hwsec_, PreloadSealedData(_)) |
| .WillRepeatedly(ReturnValue(std::nullopt)); |
| } |
| |
| void RemoveFactor(AuthSession& auth_session, |
| const std::string& label, |
| const std::string& secret) { |
| user_data_auth::RemoveAuthFactorRequest request; |
| request.set_auth_factor_label(label); |
| request.set_auth_session_id(auth_session.serialized_token()); |
| TestFuture<CryptohomeStatus> remove_future; |
| auth_session.GetAuthForDecrypt()->RemoveAuthFactor( |
| request, remove_future.GetCallback()); |
| EXPECT_THAT(remove_future.Get(), IsOk()); |
| } |
| |
| KeyData DefaultKeyData() { |
| KeyData key_data; |
| key_data.set_label(kPasswordLabel); |
| return key_data; |
| } |
| |
| void KeysetSetUpWithKeyDataAndKeyBlobs(const KeyData& key_data, |
| int index = 0) { |
| for (auto& user : users_) { |
| VaultKeyset vk; |
| vk.Initialize(&platform_, &crypto_); |
| vk.CreateFromFileSystemKeyset(file_system_keyset_); |
| vk.SetKeyData(key_data); |
| AuthBlockState auth_block_state; |
| auth_block_state.state = kTpmState; |
| |
| ASSERT_THAT(vk.EncryptEx(kKeyBlobs, auth_block_state), IsOk()); |
| ASSERT_TRUE(vk.Save(user.homedir_path.Append(kKeyFile).AddExtension( |
| std::to_string(index)))); |
| } |
| } |
| |
| void BackupKeysetSetUpWithKeyDataAndKeyBlobs(const KeyData& key_data, |
| int index = 0) { |
| for (auto& user : users_) { |
| VaultKeyset vk; |
| vk.InitializeAsBackup(&platform_, &crypto_); |
| vk.CreateFromFileSystemKeyset(file_system_keyset_); |
| vk.SetKeyData(key_data); |
| AuthBlockState auth_block_state; |
| auth_block_state.state = kTpmState; |
| |
| ASSERT_THAT(vk.EncryptEx(kKeyBlobs, auth_block_state), IsOk()); |
| ASSERT_TRUE(vk.Save(user.homedir_path.Append(kKeyFile).AddExtension( |
| std::to_string(index)))); |
| } |
| } |
| |
| void KeysetSetUpWithoutKeyDataAndKeyBlobs() { |
| for (auto& user : users_) { |
| VaultKeyset vk; |
| vk.Initialize(&platform_, &crypto_); |
| vk.CreateFromFileSystemKeyset(file_system_keyset_); |
| AuthBlockState auth_block_state = { |
| .state = kTpmState, |
| }; |
| |
| ASSERT_THAT(vk.EncryptEx(kKeyBlobs, auth_block_state), IsOk()); |
| ASSERT_TRUE( |
| vk.Save(user.homedir_path.Append(kKeyFile).AddExtension("0"))); |
| } |
| } |
| |
| VaultKeyset KeysetSetupWithAuthInput(bool is_migrated, |
| bool is_backup, |
| const AuthInput& auth_input, |
| KeyData& key_data, |
| const std::string& file_indice) { |
| VaultKeyset vk; |
| AuthBlockType auth_block_type = AuthBlockType::kTpmEcc; |
| if (key_data.has_policy() && key_data.policy().low_entropy_credential()) { |
| auth_block_type = AuthBlockType::kPinWeaver; |
| } |
| auth_block_utility_.CreateKeyBlobsWithAuthBlock( |
| auth_block_type, auth_input, |
| base::BindLambdaForTesting( |
| [&](CryptohomeStatus error, std::unique_ptr<KeyBlobs> key_blobs, |
| std::unique_ptr<AuthBlockState> auth_block_state) { |
| ASSERT_THAT(error, IsOk()); |
| vk.Initialize(&platform_, &crypto_); |
| vk.SetKeyData(key_data); |
| vk.set_backup_vk_for_testing(is_backup); |
| |
| vk.set_migrated_vk_for_testing(is_migrated); |
| vk.CreateFromFileSystemKeyset(file_system_keyset_); |
| ASSERT_THAT(vk.EncryptEx(*key_blobs, *auth_block_state), IsOk()); |
| ASSERT_TRUE( |
| vk.Save(users_[0].homedir_path.Append(kKeyFile).AddExtension( |
| file_indice))); |
| })); |
| return vk; |
| } |
| |
| AuthSession StartAuthSessionWithMockAuthBlockUtility() { |
| AuthFactorVaultKeysetConverter converter{&keyset_management_}; |
| |
| std::map<std::string, AuthFactorType> af_types = |
| auth_factor_manager_.ListAuthFactors(users_[0].obfuscated); |
| std::set<std::string_view> labels; |
| for (const auto& [label, unused] : af_types) { |
| labels.insert(label); |
| } |
| AuthFactorMap auth_factor_map = auth_factor_manager_.LoadAllAuthFactors( |
| users_[0].obfuscated, labels, converter); |
| AuthSession::Params auth_session_params{ |
| .username = users_[0].username, |
| .is_ephemeral_user = false, |
| .intent = AuthIntent::kDecrypt, |
| .auth_factor_status_update_timer = |
| std::make_unique<base::WallClockTimer>(), |
| .user_exists = true, |
| .auth_factor_map = std::move(auth_factor_map)}; |
| backing_apis_.auth_block_utility = &mock_auth_block_utility_; |
| return AuthSession(std::move(auth_session_params), backing_apis_); |
| } |
| |
| AuthSession StartAuthSession() { |
| AuthFactorVaultKeysetConverter converter{&keyset_management_}; |
| |
| std::map<std::string, AuthFactorType> af_types = |
| auth_factor_manager_.ListAuthFactors(users_[0].obfuscated); |
| std::set<std::string_view> labels; |
| for (const auto& [label, unused] : af_types) { |
| labels.insert(label); |
| } |
| AuthFactorMap auth_factor_map = auth_factor_manager_.LoadAllAuthFactors( |
| users_[0].obfuscated, labels, converter); |
| AuthSession::Params auth_session_params{ |
| .username = users_[0].username, |
| .is_ephemeral_user = false, |
| .intent = AuthIntent::kDecrypt, |
| .auth_factor_status_update_timer = |
| std::make_unique<base::WallClockTimer>(), |
| .user_exists = true, |
| .auth_factor_map = std::move(auth_factor_map)}; |
| return AuthSession(std::move(auth_session_params), backing_apis_); |
| } |
| |
| void AddFactorWithMockAuthBlockUtility(AuthSession& auth_session, |
| const std::string& label, |
| const std::string& secret) { |
| EXPECT_CALL(mock_auth_block_utility_, SelectAuthBlockTypeForCreation(_)) |
| .WillOnce(ReturnValue(AuthBlockType::kTpmEcc)); |
| auto key_blobs = std::make_unique<KeyBlobs>(kKeyBlobs); |
| auto auth_block_state = std::make_unique<AuthBlockState>(); |
| auth_block_state->state = kTpmState; |
| |
| EXPECT_CALL(mock_auth_block_utility_, CreateKeyBlobsWithAuthBlock(_, _, _)) |
| .WillOnce([&key_blobs, &auth_block_state]( |
| AuthBlockType auth_block_type, |
| const AuthInput& auth_input, |
| AuthBlock::CreateCallback create_callback) { |
| std::move(create_callback) |
| .Run(OkStatus<CryptohomeError>(), std::move(key_blobs), |
| std::move(auth_block_state)); |
| return true; |
| }); |
| user_data_auth::AddAuthFactorRequest request; |
| request.mutable_auth_factor()->set_type( |
| user_data_auth::AUTH_FACTOR_TYPE_PASSWORD); |
| request.mutable_auth_factor()->set_label(label); |
| request.mutable_auth_factor()->mutable_password_metadata(); |
| request.mutable_auth_input()->mutable_password_input()->set_secret(secret); |
| request.set_auth_session_id(auth_session.serialized_token()); |
| TestFuture<CryptohomeStatus> add_future; |
| auth_session.GetAuthForDecrypt()->AddAuthFactor(request, |
| add_future.GetCallback()); |
| EXPECT_THAT(add_future.Get(), IsOk()); |
| } |
| |
| void AuthenticateAndMigrate(AuthSession& auth_session, |
| const std::string& label, |
| const std::string& secret) { |
| EXPECT_CALL(mock_auth_block_utility_, GetAuthBlockTypeFromState(_)) |
| .WillRepeatedly(Return(AuthBlockType::kTpmEcc)); |
| |
| auto key_blobs2 = std::make_unique<KeyBlobs>(kKeyBlobs); |
| EXPECT_CALL(mock_auth_block_utility_, |
| DeriveKeyBlobsWithAuthBlock(_, _, _, _)) |
| .WillOnce([&key_blobs2](AuthBlockType auth_block_type, |
| const AuthInput& auth_input, |
| const AuthBlockState& auth_state, |
| AuthBlock::DeriveCallback derive_callback) { |
| std::move(derive_callback) |
| .Run(OkStatus<CryptohomeError>(), std::move(key_blobs2), |
| std::nullopt); |
| return true; |
| }); |
| auto key_blobs = std::make_unique<KeyBlobs>(kKeyBlobs); |
| auto auth_block_state = std::make_unique<AuthBlockState>(); |
| auth_block_state->state = kTpmState; |
| |
| EXPECT_CALL(mock_auth_block_utility_, CreateKeyBlobsWithAuthBlock(_, _, _)) |
| .WillRepeatedly([&key_blobs, &auth_block_state]( |
| AuthBlockType auth_block_type, |
| const AuthInput& auth_input, |
| AuthBlock::CreateCallback create_callback) { |
| std::move(create_callback) |
| .Run(OkStatus<CryptohomeError>(), std::move(key_blobs), |
| std::move(auth_block_state)); |
| return true; |
| }); |
| std::vector<std::string> auth_factor_labels{label}; |
| user_data_auth::AuthInput auth_input_proto; |
| auth_input_proto.mutable_password_input()->set_secret(secret); |
| SerializedUserAuthFactorTypePolicy auth_factor_type_policy( |
| {.type = *SerializeAuthFactorType( |
| DetermineFactorTypeFromAuthInput(auth_input_proto).value()), |
| .enabled_intents = {}, |
| .disabled_intents = {}}); |
| |
| AuthenticateTestFuture authenticate_future; |
| auth_session.AuthenticateAuthFactor( |
| ToAuthenticateRequest(auth_factor_labels, auth_input_proto), |
| auth_factor_type_policy, authenticate_future.GetCallback()); |
| auto& [action, status] = authenticate_future.Get(); |
| EXPECT_THAT(status, IsOk()); |
| EXPECT_EQ(action.action_type, AuthSession::PostAuthActionType::kNone); |
| } |
| |
| void AddFactor(AuthSession& auth_session, |
| const std::string& label, |
| const std::string& secret) { |
| user_data_auth::AddAuthFactorRequest request; |
| request.mutable_auth_factor()->set_type( |
| user_data_auth::AUTH_FACTOR_TYPE_PASSWORD); |
| request.mutable_auth_factor()->set_label(label); |
| request.mutable_auth_factor()->mutable_password_metadata(); |
| request.mutable_auth_input()->mutable_password_input()->set_secret(secret); |
| request.set_auth_session_id(auth_session.serialized_token()); |
| TestFuture<CryptohomeStatus> add_future; |
| auth_session.GetAuthForDecrypt()->AddAuthFactor(request, |
| add_future.GetCallback()); |
| EXPECT_THAT(add_future.Get(), IsOk()); |
| } |
| |
| void UpdateFactor(AuthSession& auth_session, |
| const std::string& label, |
| const std::string& secret) { |
| EXPECT_CALL(mock_auth_block_utility_, SelectAuthBlockTypeForCreation(_)) |
| .WillOnce(ReturnValue(AuthBlockType::kTpmEcc)); |
| auto key_blobs = std::make_unique<KeyBlobs>(kKeyBlobs); |
| auto auth_block_state = std::make_unique<AuthBlockState>(); |
| auth_block_state->state = kTpmState; |
| |
| EXPECT_CALL(mock_auth_block_utility_, CreateKeyBlobsWithAuthBlock(_, _, _)) |
| .WillOnce([&key_blobs, &auth_block_state]( |
| AuthBlockType auth_block_type, |
| const AuthInput& auth_input, |
| AuthBlock::CreateCallback create_callback) { |
| std::move(create_callback) |
| .Run(OkStatus<CryptohomeError>(), std::move(key_blobs), |
| std::move(auth_block_state)); |
| return true; |
| }); |
| user_data_auth::UpdateAuthFactorRequest request; |
| request.set_auth_session_id(auth_session.serialized_token()); |
| request.set_auth_factor_label(label); |
| request.mutable_auth_factor()->set_type( |
| user_data_auth::AUTH_FACTOR_TYPE_PASSWORD); |
| request.mutable_auth_factor()->set_label(label); |
| request.mutable_auth_factor()->mutable_password_metadata(); |
| request.mutable_auth_input()->mutable_password_input()->set_secret(secret); |
| TestFuture<CryptohomeStatus> update_future; |
| auth_session.GetAuthForDecrypt()->UpdateAuthFactor( |
| request, update_future.GetCallback()); |
| EXPECT_THAT(update_future.Get(), IsOk()); |
| } |
| |
| void AuthenticatePasswordFactor(AuthSession& auth_session, |
| const std::string& label, |
| const std::string& secret) { |
| std::vector<std::string> auth_factor_labels{label}; |
| user_data_auth::AuthInput auth_input_proto; |
| auth_input_proto.mutable_password_input()->set_secret(secret); |
| AuthenticateTestFuture authenticate_future; |
| SerializedUserAuthFactorTypePolicy auth_factor_type_policy( |
| {.type = SerializedAuthFactorType::kPassword, |
| .enabled_intents = {}, |
| .disabled_intents = {}}); |
| auth_session.AuthenticateAuthFactor( |
| ToAuthenticateRequest(auth_factor_labels, auth_input_proto), |
| auth_factor_type_policy, authenticate_future.GetCallback()); |
| auto& [action, status] = authenticate_future.Get(); |
| EXPECT_THAT(status, IsOk()); |
| EXPECT_EQ(action.action_type, AuthSession::PostAuthActionType::kNone); |
| } |
| |
| user_data_auth::CryptohomeErrorCode AttemptAuthWithPinFactor( |
| AuthSession& auth_session, |
| const std::string& label, |
| const std::string& secret) { |
| std::vector<std::string> auth_factor_labels{label}; |
| user_data_auth::AuthInput auth_input_proto; |
| auth_input_proto.mutable_pin_input()->set_secret(secret); |
| AuthenticateTestFuture authenticate_future; |
| SerializedUserAuthFactorTypePolicy auth_factor_type_policy( |
| {.type = SerializedAuthFactorType::kPin, |
| .enabled_intents = {}, |
| .disabled_intents = {}}); |
| auth_session.AuthenticateAuthFactor( |
| ToAuthenticateRequest(auth_factor_labels, auth_input_proto), |
| auth_factor_type_policy, authenticate_future.GetCallback()); |
| |
| auto& [unused_action, status] = authenticate_future.Get(); |
| if (status.ok() || !status->local_legacy_error().has_value()) { |
| return user_data_auth::CRYPTOHOME_ERROR_NOT_SET; |
| } |
| return status->local_legacy_error().value(); |
| } |
| |
| // Standard key blob and TPM state objects to use in testing. |
| const brillo::SecureBlob kBlob32{std::string(32, 'A')}; |
| const brillo::SecureBlob kBlob16{std::string(16, 'C')}; |
| const KeyBlobs kKeyBlobs{ |
| .vkk_key = kBlob32, .vkk_iv = kBlob16, .chaps_iv = kBlob16}; |
| const TpmEccAuthBlockState kTpmState = { |
| .salt = brillo::SecureBlob(kSalt), |
| .vkk_iv = kBlob32, |
| .auth_value_rounds = kAuthValueRounds, |
| .sealed_hvkkm = kBlob32, |
| .extended_sealed_hvkkm = kBlob32, |
| .tpm_public_key_hash = brillo::SecureBlob(kPublicHash), |
| }; |
| |
| base::test::TaskEnvironment task_environment_; |
| |
| // Mocks and fakes for the test AuthSessions to use. |
| NiceMock<hwsec::MockCryptohomeFrontend> hwsec_; |
| NiceMock<hwsec::MockPinWeaverFrontend> pinweaver_; |
| NiceMock<MockCryptohomeKeysManager> cryptohome_keys_manager_; |
| Crypto crypto_{&hwsec_, &pinweaver_, &cryptohome_keys_manager_, |
| /*recovery_hwsec=*/nullptr}; |
| NiceMock<MockPlatform> platform_; |
| UserSessionMap user_session_map_; |
| KeysetManagement keyset_management_{&platform_, &crypto_, |
| CreateMockVaultKeysetFactory()}; |
| FakeFeaturesForTesting features_; |
| NiceMock<MockChallengeCredentialsHelper> challenge_credentials_helper_; |
| NiceMock<MockKeyChallengeServiceFactory> key_challenge_service_factory_; |
| std::unique_ptr<FingerprintAuthBlockService> fp_service_{ |
| FingerprintAuthBlockService::MakeNullService()}; |
| AuthBlockUtilityImpl auth_block_utility_{ |
| &keyset_management_, |
| &crypto_, |
| &platform_, |
| &features_.async, |
| AsyncInitPtr<ChallengeCredentialsHelper>(&challenge_credentials_helper_), |
| &key_challenge_service_factory_, |
| AsyncInitPtr<BiometricsAuthBlockService>(nullptr)}; |
| NiceMock<MockAuthBlockUtility> mock_auth_block_utility_; |
| AuthFactorDriverManager auth_factor_driver_manager_{ |
| &platform_, |
| &crypto_, |
| AsyncInitPtr<ChallengeCredentialsHelper>(nullptr), |
| nullptr, |
| fp_service_.get(), |
| AsyncInitPtr<BiometricsAuthBlockService>(nullptr), |
| nullptr}; |
| AuthFactorManager auth_factor_manager_{&platform_}; |
| UssStorage uss_storage_{&platform_}; |
| UserMetadataReader user_metadata_reader_{&uss_storage_}; |
| AuthSession::BackingApis backing_apis_{&crypto_, |
| &platform_, |
| &user_session_map_, |
| &keyset_management_, |
| &auth_block_utility_, |
| &auth_factor_driver_manager_, |
| &auth_factor_manager_, |
| &uss_storage_, |
| &user_metadata_reader_, |
| &features_.async}; |
| |
| // An AuthSession manager for testing managed creation. |
| std::unique_ptr<AuthSessionManager> auth_session_manager_; |
| |
| FileSystemKeyset file_system_keyset_; |
| MockVaultKeysetFactory* mock_vault_keyset_factory_; |
| NiceMock<MockHomeDirs> homedirs_; |
| NiceMock<MockUserSessionFactory> user_session_factory_; |
| |
| NiceMock<MockPkcs11TokenFactory> pkcs11_token_factory_; |
| NiceMock<MockUserOldestActivityTimestampManager> |
| user_activity_timestamp_manager_; |
| NiceMock<MockInstallAttributes> install_attrs_; |
| UserDataAuth userdataauth_; |
| |
| // Store user info for users that will be setup. |
| std::vector<UserInfo> users_; |
| }; |
| |
| // This test checks if StartAuthSession can return keydataless keysets |
| // correctly. |
| TEST_F(AuthSessionTestWithKeysetManagement, StartAuthSessionWithoutKeyData) { |
| KeysetSetUpWithoutKeyDataAndKeyBlobs(); |
| |
| user_data_auth::StartAuthSessionRequest start_auth_session_req; |
| start_auth_session_req.mutable_account_id()->set_account_id( |
| *users_[0].username); |
| user_data_auth::StartAuthSessionReply auth_session_reply; |
| |
| userdataauth_.StartAuthSession( |
| start_auth_session_req, |
| base::BindOnce( |
| [](user_data_auth::StartAuthSessionReply* auth_reply_ptr, |
| const user_data_auth::StartAuthSessionReply& reply) { |
| *auth_reply_ptr = reply; |
| }, |
| base::Unretained(&auth_session_reply))); |
| |
| EXPECT_EQ(auth_session_reply.error(), |
| user_data_auth::CRYPTOHOME_ERROR_NOT_SET); |
| std::optional<base::UnguessableToken> auth_session_id = |
| AuthSession::GetTokenFromSerializedString( |
| auth_session_reply.auth_session_id()); |
| EXPECT_TRUE(auth_session_id.has_value()); |
| InUseAuthSession auth_session = |
| userdataauth_.auth_session_manager_->FindAuthSession( |
| auth_session_id.value()); |
| EXPECT_TRUE(auth_session.AuthSessionStatus().ok()); |
| } |
| |
| // Test that a VaultKeyset without KeyData migration succeeds during login. |
| TEST_F(AuthSessionTestWithKeysetManagement, |
| MigrationToUssWithNoKeyDataAndNewFactor) { |
| // Setup |
| // Setup legacy VaultKeysets to test USS migration. On AuthSession start |
| // legacy keyset should be migrated to USS following successful |
| // authentication. |
| SetUpHwsecAuthenticationMocks(); |
| AuthInput auth_input = { |
| .user_input = brillo::SecureBlob(kPassword), |
| .locked_to_single_user = std::nullopt, |
| .username = users_[0].username, |
| .obfuscated_username = users_[0].obfuscated, |
| }; |
| auth_block_utility_.CreateKeyBlobsWithAuthBlock( |
| AuthBlockType::kTpmEcc, auth_input, |
| base::BindLambdaForTesting( |
| [&](CryptohomeStatus error, std::unique_ptr<KeyBlobs> key_blobs, |
| std::unique_ptr<AuthBlockState> auth_block_state) { |
| ASSERT_THAT(error, IsOk()); |
| VaultKeyset vk; |
| vk.Initialize(&platform_, &crypto_); |
| vk.CreateFromFileSystemKeyset(file_system_keyset_); |
| ASSERT_THAT(vk.EncryptEx(*key_blobs, *auth_block_state), IsOk()); |
| ASSERT_TRUE(vk.Save( |
| users_[0].homedir_path.Append(kKeyFile).AddExtension("0"))); |
| })); |
| |
| AuthSession auth_session1 = StartAuthSession(); |
| ASSERT_EQ(auth_session1.auth_factor_map().Find(kDefaultLabel)->storage_type(), |
| AuthFactorStorageType::kVaultKeyset); |
| // Test that authenticating the password migrates VaultKeyset to |
| // UserSecretStash. |
| AuthenticatePasswordFactor(auth_session1, kDefaultLabel, kPassword); |
| ASSERT_EQ(auth_session1.auth_factor_map().Find(kDefaultLabel)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| |
| // Verify that migrator created the user_secret_stash and uss_main_key. |
| ASSERT_TRUE(auth_session1.has_user_secret_stash()); |
| |
| // Verify that the authentication succeeds after migration. |
| AuthSession auth_session2 = StartAuthSession(); |
| EXPECT_THAT(auth_session2.authorized_intents(), IsEmpty()); |
| ASSERT_EQ(auth_session2.auth_factor_map().Find(kDefaultLabel)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| AuthenticatePasswordFactor(auth_session2, kDefaultLabel, kPassword); |
| |
| // Test that adding a new factor succeeds. |
| AuthSession auth_session4 = StartAuthSession(); |
| ASSERT_EQ(auth_session4.auth_factor_map().Find(kDefaultLabel)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| AuthenticatePasswordFactor(auth_session4, kDefaultLabel, kPassword); |
| AddFactor(auth_session4, kPasswordLabel2, kPassword2); |
| ASSERT_EQ( |
| auth_session4.auth_factor_map().Find(kPasswordLabel2)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| // Verify authentication works with the added factor. |
| AuthenticatePasswordFactor(auth_session4, kPasswordLabel2, kPassword2); |
| } |
| |
| // This test tests successful removal of the backup keysets. |
| // Initial USS migration code converted the migrated VaultKeysets to backup |
| // keysets rather than removing them. |
| // USS migration code is then updated to remove the migrated VaultKeysets since |
| // the rollback is no longer a possibility. Updated USS migration code also |
| // removes the leftover backup keysets from initial USS migration. |
| // This test tests the removal of the leftover backup keyset is successful and |
| // removing the backup keyset doesn't break the PIN lock/unlock mechanism. |
| TEST_F(AuthSessionTestWithKeysetManagement, |
| RemoveBackupKeysetFromMigratedKeyset) { |
| // SETUP |
| constexpr char kCredDirName[] = "low_entropy_creds"; |
| constexpr int kMaxWrongAttempts = 6; |
| |
| // Setup low entropy credential manager. |
| base::ScopedTempDir temp_dir; |
| EXPECT_TRUE(temp_dir.CreateUniqueTempDir()); |
| base::FilePath temp_path = temp_dir.GetPath().Append(kCredDirName); |
| hwsec::Tpm2SimulatorFactoryForTest factory; |
| std::unique_ptr<const hwsec::PinWeaverFrontend> pinweaver = |
| factory.GetPinWeaverFrontend(); |
| auto le_cred_manager = |
| std::make_unique<LECredentialManagerImpl>(pinweaver.get(), temp_path); |
| crypto_.set_le_manager_for_testing(std::move(le_cred_manager)); |
| crypto_.Init(); |
| |
| SetUpHwsecAuthenticationMocks(); |
| AuthInput auth_input = { |
| .user_input = brillo::SecureBlob(kPassword), |
| .locked_to_single_user = std::nullopt, |
| .username = users_[0].username, |
| .obfuscated_username = users_[0].obfuscated, |
| }; |
| |
| // Setup keyset files. |
| |
| KeyData key_data = DefaultKeyData(); |
| |
| // Setup keyset file to be used as backup keyset simulator. |
| key_data.set_label(kDefaultLabel); |
| auto backup_vk = KeysetSetupWithAuthInput( |
| /*is_migrated=*/true, /*is_backup=*/true, auth_input, key_data, "1"); |
| brillo::SecureBlob reset_seed = backup_vk.GetResetSeed(); |
| |
| // Setup original keyset. |
| key_data.set_label(kPasswordLabel); |
| KeysetSetupWithAuthInput( |
| /*is_migrated=*/false, /*is_backup=*/false, auth_input, key_data, "0"); |
| |
| // Test authenticate migrates to UserSecretStash. |
| |
| // AuthenticateAuthFactor also removes the original keyset but not the backup |
| // keyset simulator, since it has a different label. |
| { |
| AuthSession auth_session = StartAuthSession(); |
| EXPECT_THAT(auth_session.authorized_intents(), IsEmpty()); |
| EXPECT_TRUE(auth_session.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| EXPECT_FALSE(auth_session.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| |
| AuthenticatePasswordFactor(auth_session, kPasswordLabel, kPassword); |
| EXPECT_EQ(nullptr, keyset_management_.GetVaultKeyset(users_[0].obfuscated, |
| kPasswordLabel)); |
| EXPECT_NE(nullptr, keyset_management_.GetVaultKeyset(users_[0].obfuscated, |
| kDefaultLabel)); |
| } |
| |
| // Simulate backup keyset. |
| |
| // Setup backup keyset to simulate the state when migrated factors |
| // had backup keysets. Restore original label since now the regular |
| // VaultKeyset with the original label is migrated to USS and the regular |
| // VaultKeyset is deleted. |
| std::unique_ptr<VaultKeyset> vk_backup = |
| keyset_management_.GetVaultKeyset(users_[0].obfuscated, kDefaultLabel); |
| EXPECT_NE(nullptr, vk_backup); |
| vk_backup->SetKeyDataLabel(kPasswordLabel); |
| ASSERT_TRUE(vk_backup->Save( |
| users_[0].homedir_path.Append(kKeyFile).AddExtension("0"))); |
| EXPECT_NE(nullptr, keyset_management_.GetVaultKeyset(users_[0].obfuscated, |
| kPasswordLabel)); |
| |
| // Simulate the mixed configuration. |
| |
| // Setup a PIN keyset to simulate a mixed configuration of VaultKeyset and USS |
| // backed factors. |
| auth_input.user_input = brillo::SecureBlob(kPin); |
| auth_input.reset_seed = reset_seed; |
| EXPECT_CALL(hwsec_, IsPinWeaverEnabled()).WillRepeatedly(ReturnValue(true)); |
| KeyData pin_data; |
| pin_data.set_label(kPinLabel); |
| pin_data.mutable_policy()->set_low_entropy_credential(true); |
| KeysetSetupWithAuthInput( |
| /*is_migrated=*/false, /*is_backup=*/false, auth_input, pin_data, "1"); |
| // Verify mixed configuration state. |
| std::unique_ptr<VaultKeyset> vk_password = |
| keyset_management_.GetVaultKeyset(users_[0].obfuscated, kPasswordLabel); |
| std::unique_ptr<VaultKeyset> vk_pin = |
| keyset_management_.GetVaultKeyset(users_[0].obfuscated, kPinLabel); |
| |
| EXPECT_NE(nullptr, vk_password); |
| EXPECT_NE(nullptr, vk_pin); |
| EXPECT_TRUE(vk_password->IsForBackup()); |
| EXPECT_TRUE(vk_password->IsMigrated()); |
| EXPECT_FALSE(vk_pin->IsForBackup()); |
| |
| // Test that AuthenticateAuthFactor removes the backup keyset. |
| |
| // We need to mock the KeysetManagement. Encryption key of the USS |
| // key_block and the VaultKeyset are different since the backup is not |
| // generated during the migration flow. Hence VaultKeyset can't be decrypted |
| // by the same authentication. |
| auto original_backing_apis = std::move(backing_apis_); |
| NiceMock<MockKeysetManagement> mock_keyset_management; |
| AuthSession::BackingApis backing_api_with_mock_km{ |
| &crypto_, |
| &platform_, |
| &user_session_map_, |
| &mock_keyset_management, |
| &auth_block_utility_, |
| &auth_factor_driver_manager_, |
| &auth_factor_manager_, |
| &uss_storage_, |
| &user_metadata_reader_, |
| &features_.async}; |
| backing_apis_ = std::move(backing_api_with_mock_km); |
| |
| { |
| AuthSession auth_session = StartAuthSession(); |
| EXPECT_TRUE(auth_session.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| EXPECT_TRUE(auth_session.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| EXPECT_CALL(mock_keyset_management, GetVaultKeyset(users_[0].obfuscated, _)) |
| .WillRepeatedly( |
| [&](const ObfuscatedUsername& obfuscated, const std::string&) { |
| std::unique_ptr<VaultKeyset> vk_to_mock = |
| keyset_management_.GetVaultKeyset(obfuscated, kPinLabel); |
| vk_to_mock->SetResetSalt(vk_pin->GetResetSalt()); |
| vk_to_mock->set_backup_vk_for_testing(true); |
| return vk_to_mock; |
| }); |
| EXPECT_CALL(mock_keyset_management, RemoveKeysetFile(_)) |
| .WillOnce(Return(OkStatus<CryptohomeError>())); |
| // We need to explicitly add the reset_seed for testing since |vk_password| |
| // is not decrypted. |
| vk_password->SetResetSeed(reset_seed); |
| EXPECT_CALL(mock_keyset_management, GetValidKeyset(_, _, _)) |
| .WillOnce(Return(std::move(vk_password))); |
| AuthenticatePasswordFactor(auth_session, kPasswordLabel, kPassword); |
| } |
| |
| // Verify PIN reset mechanism. |
| |
| // Verify that wrong PINs lock the PIN counter and password authentication |
| // reset the PIN counter after the removal of the backup password. |
| |
| backing_apis_ = std::move(original_backing_apis); |
| { |
| AuthSession auth_session = StartAuthSession(); |
| EXPECT_TRUE(auth_session.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| EXPECT_TRUE(auth_session.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| AuthenticatePasswordFactor(auth_session, kPasswordLabel, kPassword); |
| |
| // Attempting too many wrong PINs, but don't lock yet. |
| for (int i = 0; i < kMaxWrongAttempts - 2; i++) { |
| EXPECT_EQ(user_data_auth::CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED, |
| AttemptAuthWithPinFactor(auth_session, kPinLabel, kWrongPin)); |
| } |
| // One more wrong PIN attempt will lockout the PIN. |
| EXPECT_EQ(user_data_auth::CRYPTOHOME_ERROR_CREDENTIAL_LOCKED, |
| AttemptAuthWithPinFactor(auth_session, kPinLabel, kWrongPin)); |
| // Pin should be locked and correct PIN should fail. |
| EXPECT_EQ(user_data_auth::CRYPTOHOME_ERROR_TPM_DEFEND_LOCK, |
| AttemptAuthWithPinFactor(auth_session, kPinLabel, kPin)); |
| // Reset the PIN counter with correct password. |
| AuthenticatePasswordFactor(auth_session, kPasswordLabel, kPassword); |
| // After resetting with password correct PIn should now work. |
| EXPECT_EQ(user_data_auth::CRYPTOHOME_ERROR_NOT_SET, |
| AttemptAuthWithPinFactor(auth_session, kPinLabel, kPin)); |
| } |
| } |
| |
| // Test that we can authenticate an old-style kiosk VK, and migrate it to USS |
| // correctly. These old VKs show up as password VKs and so we need the |
| // authenticate to successfully convert it to a kiosk based on the input. |
| TEST_F(AuthSessionTestWithKeysetManagement, AuthenticatePasswordVkToKioskUss) { |
| // Setup |
| // Setup legacy kiosk VaultKeyset to test USS migration. |
| SetUpHwsecAuthenticationMocks(); |
| user_data_auth::AuthInput proto; |
| proto.mutable_kiosk_input(); |
| AuthFactorMetadata auth_factor_metadata; |
| std::optional<AuthInput> auth_input = CreateAuthInput( |
| &platform_, proto, users_[0].username, users_[0].obfuscated, |
| /*locked_to_single_user=*/true, |
| /*cryptohome_recovery_ephemeral_pub_key=*/std::nullopt, |
| auth_factor_metadata); |
| auth_block_utility_.CreateKeyBlobsWithAuthBlock( |
| AuthBlockType::kTpmEcc, auth_input.value(), |
| base::BindLambdaForTesting( |
| [&](CryptohomeStatus error, std::unique_ptr<KeyBlobs> key_blobs, |
| std::unique_ptr<AuthBlockState> auth_block_state) { |
| ASSERT_THAT(error, IsOk()); |
| VaultKeyset vk; |
| vk.Initialize(&platform_, &crypto_); |
| vk.CreateFromFileSystemKeyset(file_system_keyset_); |
| ASSERT_THAT(vk.EncryptEx(*key_blobs, *auth_block_state), IsOk()); |
| ASSERT_TRUE(vk.Save( |
| users_[0].homedir_path.Append(kKeyFile).AddExtension("0"))); |
| })); |
| AuthSession auth_session = StartAuthSession(); |
| ASSERT_EQ(auth_session.auth_factor_map().Find(kDefaultLabel)->storage_type(), |
| AuthFactorStorageType::kVaultKeyset); |
| |
| // Test that authenticating the legacy kiosk migrates VaultKeyset to |
| // UserSecretStash as a kKiosk type. |
| std::vector<std::string> auth_factor_labels{kDefaultLabel}; |
| user_data_auth::AuthInput auth_input_proto; |
| auth_input_proto.mutable_kiosk_input(); |
| AuthenticateTestFuture authenticate_future; |
| SerializedUserAuthFactorTypePolicy auth_factor_type_policy( |
| {.type = *SerializeAuthFactorType( |
| DetermineFactorTypeFromAuthInput(auth_input_proto).value()), |
| .enabled_intents = {}, |
| .disabled_intents = {}}); |
| auth_session.AuthenticateAuthFactor( |
| ToAuthenticateRequest(auth_factor_labels, auth_input_proto), |
| auth_factor_type_policy, authenticate_future.GetCallback()); |
| auto& [action, status] = authenticate_future.Get(); |
| EXPECT_THAT(status, IsOk()); |
| EXPECT_EQ(action.action_type, AuthSession::PostAuthActionType::kNone); |
| |
| ASSERT_EQ(auth_session.auth_factor_map().Find(kDefaultLabel)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| |
| // Verify. |
| ASSERT_TRUE(auth_session.has_user_secret_stash()); |
| ASSERT_THAT(auth_session.auth_factor_map().size(), Eq(1)); |
| AuthFactorMap::ValueView stored_auth_factor = |
| *auth_session.auth_factor_map().begin(); |
| const AuthFactor& auth_factor = stored_auth_factor.auth_factor(); |
| EXPECT_THAT(stored_auth_factor.storage_type(), |
| Eq(AuthFactorStorageType::kUserSecretStash)); |
| EXPECT_THAT(auth_factor.type(), Eq(AuthFactorType::kKiosk)); |
| EXPECT_THAT(auth_factor.metadata().metadata, |
| VariantWith<auth_factor::KioskMetadata>(_)); |
| } |
| |
| // Test if AuthenticateAuthFactor authenticates existing credentials for a |
| // user with VK and resaves it. |
| TEST_F(AuthSessionTestWithKeysetManagement, |
| AuthenticateAuthFactorExistingVKAndResaves) { |
| // Setup |
| // Setup legacy VaultKeyset with no chaps key so that AuthenticateAuthFactor |
| // generates a chaps key and saves it before migrating to USS. |
| SetUpHwsecAuthenticationMocks(); |
| AuthInput auth_input = { |
| .user_input = brillo::SecureBlob(kPassword), |
| .locked_to_single_user = std::nullopt, |
| .username = users_[0].username, |
| .obfuscated_username = users_[0].obfuscated, |
| }; |
| VaultKeyset vk; |
| auth_block_utility_.CreateKeyBlobsWithAuthBlock( |
| AuthBlockType::kTpmEcc, auth_input, |
| base::BindLambdaForTesting( |
| [&](CryptohomeStatus error, std::unique_ptr<KeyBlobs> key_blobs, |
| std::unique_ptr<AuthBlockState> auth_block_state) { |
| ASSERT_THAT(error, IsOk()); |
| vk.Initialize(&platform_, &crypto_); |
| KeyData key_data; |
| key_data.set_label(kDefaultLabel); |
| vk.SetKeyData(key_data); |
| vk.CreateFromFileSystemKeyset(file_system_keyset_); |
| ASSERT_THAT(vk.EncryptEx(*key_blobs, *auth_block_state), IsOk()); |
| vk.ClearWrappedChapsKey(); |
| ASSERT_TRUE(vk.Save( |
| users_[0].homedir_path.Append(kKeyFile).AddExtension("0"))); |
| })); |
| |
| AuthSession auth_session = StartAuthSession(); |
| ASSERT_EQ(auth_session.auth_factor_map().Find(kDefaultLabel)->storage_type(), |
| AuthFactorStorageType::kVaultKeyset); |
| ASSERT_FALSE(vk.HasWrappedChapsKey()); |
| |
| // Test that authenticating the VaultKeyset with missing chaps key still |
| // migrates to UserSecretStash after regenerating the chaps key. Note |
| // AuthenticateAuthFactor() returning success shows that chaps key has been |
| // generated on VK and resaved. Otherwise USS creation during migration would |
| // fail. |
| AuthenticatePasswordFactor(auth_session, kDefaultLabel, kPassword); |
| ASSERT_EQ(auth_session.auth_factor_map().Find(kDefaultLabel)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| |
| // Verify that migrator created the user_secret_stash and uss_main_key. |
| ASSERT_TRUE(auth_session.has_user_secret_stash()); |
| } |
| |
| // Test that a VaultKeyset without KeyData migration succeeds during login. |
| TEST_F(AuthSessionTestWithKeysetManagement, MigrationToUssWithNoKeyData) { |
| // Setup |
| // Setup legacy VaultKeysets to test USS migration. On AuthSession start |
| // legacy keyset should be migrated to USS following successful |
| // authentication. |
| KeysetSetUpWithoutKeyDataAndKeyBlobs(); |
| |
| AuthSession auth_session = StartAuthSessionWithMockAuthBlockUtility(); |
| EXPECT_THAT(auth_session.authorized_intents(), IsEmpty()); |
| |
| // Test that authenticating the password migrates VaultKeyset to |
| // UserSecretStash, converting the VaultKeyset to a backup VaultKeyset. |
| EXPECT_TRUE(auth_session.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| EXPECT_FALSE(auth_session.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| AuthenticateAndMigrate(auth_session, kDefaultLabel, kPassword); |
| |
| // Verify that migrator created the user_secret_stash and uss_main_key. |
| UssStorage uss_storage(&platform_); |
| UserUssStorage user_uss_storage(uss_storage, users_[0].obfuscated); |
| std::optional<brillo::SecureBlob> uss_credential_secret = |
| kKeyBlobs.DeriveUssCredentialSecret(); |
| ASSERT_TRUE(uss_credential_secret.has_value()); |
| CryptohomeStatusOr<DecryptedUss> decrypted_uss = |
| DecryptedUss::FromStorageUsingWrappedKey(user_uss_storage, kDefaultLabel, |
| *uss_credential_secret); |
| ASSERT_THAT(decrypted_uss, IsOk()); |
| |
| // Verify that the user_secret_stash has the wrapped_key_block for |
| // the default label. |
| ASSERT_THAT(decrypted_uss->encrypted().WrappedMainKeyIds(), |
| Contains(kDefaultLabel)); |
| // Verify that the AuthFactors are created for the AuthFactor labels and |
| // storage type is updated in the AuthFactor map for each of them. |
| std::map<std::string, AuthFactorType> factor_map = |
| auth_factor_manager_.ListAuthFactors(users_[0].obfuscated); |
| ASSERT_NE(factor_map.find(kDefaultLabel), factor_map.end()); |
| ASSERT_EQ(auth_session.auth_factor_map().Find(kDefaultLabel)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| |
| // Verify that the authentication succeeds after migration. |
| AuthSession auth_session2 = StartAuthSessionWithMockAuthBlockUtility(); |
| EXPECT_THAT(auth_session2.authorized_intents(), IsEmpty()); |
| |
| // Test that authenticating the password should migrate VaultKeyset to |
| // UserSecretStash, converting the VaultKeyset to a backup VaultKeyset. |
| EXPECT_FALSE(auth_session2.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| EXPECT_TRUE(auth_session2.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| AuthenticateAndMigrate(auth_session2, kDefaultLabel, kPassword); |
| } |
| |
| // Test UpdateAuthFactor for partially migrated users. |
| TEST_F(AuthSessionTestWithKeysetManagement, MigrationEnabledUpdateBackup) { |
| // Setup |
| // Setup legacy VaultKeysets to test USS migration. On AuthSession start |
| // legacy keyset should be migrated to USS following successful |
| // authentication. |
| KeysetSetUpWithKeyDataAndKeyBlobs(DefaultKeyData()); |
| KeyData key_data = DefaultKeyData(); |
| key_data.set_label(kPasswordLabel2); |
| KeysetSetUpWithKeyDataAndKeyBlobs(key_data, 1); |
| |
| // Test that authenticating the password should migrate VaultKeyset to |
| // UserSecretStash. |
| AuthSession auth_session2 = StartAuthSessionWithMockAuthBlockUtility(); |
| EXPECT_TRUE(auth_session2.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| EXPECT_FALSE(auth_session2.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| AuthenticateAndMigrate(auth_session2, kPasswordLabel, kPassword); |
| |
| // Verify that migrator loaded the user_secret_stash and uss_main_key. |
| UssStorage uss_storage(&platform_); |
| UserUssStorage user_uss_storage(uss_storage, users_[0].obfuscated); |
| std::optional<brillo::SecureBlob> uss_credential_secret = |
| kKeyBlobs.DeriveUssCredentialSecret(); |
| ASSERT_TRUE(uss_credential_secret.has_value()); |
| CryptohomeStatusOr<DecryptedUss> decrypted_uss = |
| DecryptedUss::FromStorageUsingWrappedKey(user_uss_storage, kPasswordLabel, |
| *uss_credential_secret); |
| ASSERT_THAT(decrypted_uss, IsOk()); |
| |
| // Verify that the user_secret_stash has the wrapped_key_blocks for the |
| // AuthFactor label. |
| ASSERT_THAT(decrypted_uss->encrypted().WrappedMainKeyIds(), |
| Contains(kPasswordLabel)); |
| // Verify that the AuthFactors are created for the AuthFactor labels and |
| // storage type is updated in the AuthFactor map for each of them. |
| ASSERT_EQ( |
| auth_session2.auth_factor_map().Find(kPasswordLabel)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| ASSERT_EQ( |
| auth_session2.auth_factor_map().Find(kPasswordLabel2)->storage_type(), |
| AuthFactorStorageType::kVaultKeyset); |
| |
| // Test |
| UpdateFactor(auth_session2, kPasswordLabel2, kPassword2); |
| |
| // Verify AuthFactors listing. All factors are migrated. |
| AuthSession auth_session3 = StartAuthSessionWithMockAuthBlockUtility(); |
| EXPECT_TRUE(auth_session3.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| EXPECT_FALSE(auth_session3.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| ASSERT_EQ( |
| auth_session3.auth_factor_map().Find(kPasswordLabel)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| ASSERT_EQ( |
| auth_session3.auth_factor_map().Find(kPasswordLabel2)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| } |
| |
| // Test that VaultKeysets are migrated to UserSecretStash when migration is |
| // enabled, converting the existing VaultKeysets to migrated VaultKeysets. |
| TEST_F(AuthSessionTestWithKeysetManagement, MigrationEnabledMigratesToUss) { |
| // Setup |
| // Setup legacy VaultKeysets to test USS migration. On AuthSession start |
| // legacy keyset should be migrated to USS following successful |
| // authentication. |
| KeysetSetUpWithKeyDataAndKeyBlobs(DefaultKeyData()); |
| KeyData key_data = DefaultKeyData(); |
| key_data.set_label(kPasswordLabel2); |
| KeysetSetUpWithKeyDataAndKeyBlobs(key_data, 1); |
| |
| AuthSession auth_session2 = StartAuthSessionWithMockAuthBlockUtility(); |
| EXPECT_TRUE(auth_session2.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| EXPECT_FALSE(auth_session2.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| // Test that authenticating the password should migrate VaultKeyset to |
| // UserSecretStash, converting the VaultKeyset to a backup VaultKeyset. |
| AuthenticateAndMigrate(auth_session2, kPasswordLabel, kPassword); |
| AuthSession auth_session3 = StartAuthSessionWithMockAuthBlockUtility(); |
| AuthenticateAndMigrate(auth_session3, kPasswordLabel2, kPassword2); |
| |
| // Verify |
| // Verify that migrator loaded the user_secret_stash and uss_main_key. |
| UssStorage uss_storage(&platform_); |
| UserUssStorage user_uss_storage(uss_storage, users_[0].obfuscated); |
| std::optional<brillo::SecureBlob> uss_credential_secret = |
| kKeyBlobs.DeriveUssCredentialSecret(); |
| ASSERT_TRUE(uss_credential_secret.has_value()); |
| CryptohomeStatusOr<DecryptedUss> decrypted_uss = |
| DecryptedUss::FromStorageUsingWrappedKey(user_uss_storage, kPasswordLabel, |
| *uss_credential_secret); |
| ASSERT_THAT(decrypted_uss, IsOk()); |
| |
| // Verify that the user_secret_stash has the wrapped_key_blocks for the |
| // AuthFactor labels. |
| ASSERT_THAT(decrypted_uss->encrypted().WrappedMainKeyIds(), |
| Contains(kPasswordLabel)); |
| ASSERT_THAT(decrypted_uss->encrypted().WrappedMainKeyIds(), |
| Contains(kPasswordLabel2)); |
| // Verify that the AuthFactors are created for the AuthFactor labels and |
| // storage type is updated in the AuthFactor map for each of them. |
| ASSERT_EQ( |
| auth_session3.auth_factor_map().Find(kPasswordLabel)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| ASSERT_EQ( |
| auth_session3.auth_factor_map().Find(kPasswordLabel2)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| } |
| |
| // Test that after a VaultKeyset is migrated to UserSecretStash the next |
| // factor is added as USS factor. |
| TEST_F(AuthSessionTestWithKeysetManagement, |
| MigrationEnabledAddNextFactorsToUss) { |
| // Setup |
| // Setup legacy VaultKeysets to test USS migration. On AuthSession start |
| // legacy keyset should be migrated to USS following successful |
| // authentication. |
| KeysetSetUpWithKeyDataAndKeyBlobs(DefaultKeyData()); |
| |
| AuthSession auth_session2 = StartAuthSessionWithMockAuthBlockUtility(); |
| EXPECT_TRUE(auth_session2.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| EXPECT_FALSE(auth_session2.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| // Test that authenticating the password should migrate VaultKeyset to |
| // UserSecretStash, converting the VaultKeyset to a backup VaultKeyset. |
| AuthenticateAndMigrate(auth_session2, kPasswordLabel, kPassword); |
| |
| // Test that adding a second factor adds as a USS AuthFactor. |
| AddFactorWithMockAuthBlockUtility(auth_session2, kPasswordLabel2, kPassword2); |
| |
| // Verify |
| // Create a new AuthSession for verifications. |
| AuthSession auth_session3 = StartAuthSessionWithMockAuthBlockUtility(); |
| AuthenticateAndMigrate(auth_session3, kPasswordLabel2, kPassword2); |
| |
| // Verify that migrator created the user_secret_stash and uss_main_key. |
| UssStorage uss_storage(&platform_); |
| UserUssStorage user_uss_storage(uss_storage, users_[0].obfuscated); |
| std::optional<brillo::SecureBlob> uss_credential_secret = |
| kKeyBlobs.DeriveUssCredentialSecret(); |
| ASSERT_TRUE(uss_credential_secret.has_value()); |
| CryptohomeStatusOr<DecryptedUss> decrypted_uss = |
| DecryptedUss::FromStorageUsingWrappedKey(user_uss_storage, kPasswordLabel, |
| *uss_credential_secret); |
| ASSERT_THAT(decrypted_uss, IsOk()); |
| |
| // Verify that the user_secret_stash has the wrapped_key_blocks for both |
| // AuthFactor labels. |
| ASSERT_THAT(decrypted_uss->encrypted().WrappedMainKeyIds(), |
| Contains(kPasswordLabel)); |
| ASSERT_THAT(decrypted_uss->encrypted().WrappedMainKeyIds(), |
| Contains(kPasswordLabel2)); |
| // Verify that the AuthFactors are created for the AuthFactor labels and |
| // storage type is updated in the AuthFactor map for each of them. |
| std::map<std::string, AuthFactorType> factor_map = |
| auth_factor_manager_.ListAuthFactors(users_[0].obfuscated); |
| ASSERT_NE(factor_map.find(kPasswordLabel), factor_map.end()); |
| ASSERT_NE(factor_map.find(kPasswordLabel2), factor_map.end()); |
| ASSERT_EQ( |
| auth_session3.auth_factor_map().Find(kPasswordLabel)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| ASSERT_EQ( |
| auth_session3.auth_factor_map().Find(kPasswordLabel2)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| } |
| |
| // Test that AuthSession's auth factor map lists the factor from right backing |
| // store during the migration. |
| TEST_F(AuthSessionTestWithKeysetManagement, |
| AuthFactorMapStatusDuringMigration) { |
| // Setup |
| // Setup legacy VaultKeysets to test USS migration. On AuthSession start |
| // legacy keyset should be migrated to USS following successful |
| // authentication. |
| KeysetSetUpWithKeyDataAndKeyBlobs(DefaultKeyData()); |
| KeyData key_data = DefaultKeyData(); |
| key_data.set_label(kPasswordLabel2); |
| KeysetSetUpWithKeyDataAndKeyBlobs(key_data, 1); |
| |
| AuthSession auth_session = StartAuthSessionWithMockAuthBlockUtility(); |
| EXPECT_THAT(auth_session.authorized_intents(), IsEmpty()); |
| EXPECT_TRUE(auth_session.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| EXPECT_FALSE(auth_session.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| ASSERT_EQ(auth_session.auth_factor_map().Find(kPasswordLabel)->storage_type(), |
| AuthFactorStorageType::kVaultKeyset); |
| ASSERT_EQ( |
| auth_session.auth_factor_map().Find(kPasswordLabel2)->storage_type(), |
| AuthFactorStorageType::kVaultKeyset); |
| |
| // Tests |
| |
| // 1- Test migration of the first factor. Storage type for the migrated factor |
| // should be KUserSecretStash and non-migrated factor should be kVaultKeyset. |
| AuthSession auth_session2 = StartAuthSessionWithMockAuthBlockUtility(); |
| AuthenticateAndMigrate(auth_session2, kPasswordLabel, kPassword); |
| // auth_session3 should list both the migrated factor and the not migrated |
| // VK |
| AuthSession auth_session3 = StartAuthSessionWithMockAuthBlockUtility(); |
| EXPECT_TRUE(auth_session3.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| EXPECT_TRUE(auth_session3.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| ASSERT_EQ( |
| auth_session3.auth_factor_map().Find(kPasswordLabel)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| ASSERT_EQ( |
| auth_session3.auth_factor_map().Find(kPasswordLabel2)->storage_type(), |
| AuthFactorStorageType::kVaultKeyset); |
| |
| // 2- Test migration of the second factor on auth_session3. Storage type for |
| // the migrated factors should be KUserSecretStash. |
| AuthenticateAndMigrate(auth_session3, kPasswordLabel2, kPassword2); |
| EXPECT_FALSE(auth_session3.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| EXPECT_TRUE(auth_session3.auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| ASSERT_EQ( |
| auth_session3.auth_factor_map().Find(kPasswordLabel)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| ASSERT_EQ( |
| auth_session3.auth_factor_map().Find(kPasswordLabel2)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| } |
| |
| // Test that AuthSession's auth factor map lists the factor from right backing |
| // store on session start when USS is enabled. |
| TEST_F(AuthSessionTestWithKeysetManagement, AuthFactorMapUserSecretStash) { |
| // Setup |
| int flags = user_data_auth::AuthSessionFlags::AUTH_SESSION_FLAGS_NONE; |
| // Attach the mock_auth_block_utility to our AuthSessionManager and created |
| // AuthSession. |
| auto auth_session_manager_mock = |
| std::make_unique<AuthSessionManager>(AuthSession::BackingApis{ |
| &crypto_, &platform_, &user_session_map_, &keyset_management_, |
| &mock_auth_block_utility_, &auth_factor_driver_manager_, |
| &auth_factor_manager_, &uss_storage_, &user_metadata_reader_, |
| &features_.async}); |
| CryptohomeStatusOr<InUseAuthSession> auth_session_status = |
| auth_session_manager_mock->CreateAuthSession(Username(kUsername), flags, |
| AuthIntent::kDecrypt); |
| EXPECT_TRUE(auth_session_status.ok()); |
| AuthSession* auth_session = auth_session_status.value().Get(); |
| EXPECT_THAT(auth_session->authorized_intents(), IsEmpty()); |
| EXPECT_TRUE(auth_session->OnUserCreated().ok()); |
| EXPECT_THAT( |
| auth_session->authorized_intents(), |
| UnorderedElementsAre(AuthIntent::kDecrypt, AuthIntent::kVerifyOnly)); |
| EXPECT_FALSE(auth_session->auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| EXPECT_FALSE(auth_session->auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| |
| // Test |
| // Test that adding AuthFactors update the map to contain |
| // these AuthFactors with kUserSecretStash backing store. |
| AddFactorWithMockAuthBlockUtility(*auth_session, kPasswordLabel, kPassword); |
| EXPECT_FALSE(auth_session->auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| EXPECT_TRUE(auth_session->auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| AddFactorWithMockAuthBlockUtility(*auth_session, kPasswordLabel2, kPassword2); |
| EXPECT_FALSE(auth_session->auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset)); |
| EXPECT_TRUE(auth_session->auth_factor_map().HasFactorWithStorage( |
| AuthFactorStorageType::kUserSecretStash)); |
| |
| // Verify that the |auth_factor_map| contains the two labels with |
| // kUserSecretStash backing store. |
| ASSERT_EQ( |
| auth_session->auth_factor_map().Find(kPasswordLabel)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| ASSERT_EQ( |
| auth_session->auth_factor_map().Find(kPasswordLabel2)->storage_type(), |
| AuthFactorStorageType::kUserSecretStash); |
| } |
| |
| } // namespace cryptohome |