| // Copyright 2020 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cryptohome/keyset_management.h" |
| |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include <base/files/file_path.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/stringprintf.h> |
| #include <brillo/cryptohome.h> |
| #include <brillo/data_encoding.h> |
| #include <brillo/secure_blob.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "cryptohome/credentials.h" |
| #include "cryptohome/crypto.h" |
| #include "cryptohome/cryptolib.h" |
| #include "cryptohome/filesystem_layout.h" |
| #include "cryptohome/mock_le_credential_manager.h" |
| #include "cryptohome/mock_platform.h" |
| #include "cryptohome/mock_tpm.h" |
| #include "cryptohome/mock_tpm_init.h" |
| #include "cryptohome/mock_vault_keyset.h" |
| #include "cryptohome/mock_vault_keyset_factory.h" |
| #include "cryptohome/signed_secret.pb.h" |
| #include "cryptohome/vault_keyset.h" |
| |
| using ::testing::_; |
| using ::testing::ContainerEq; |
| using ::testing::ElementsAre; |
| using ::testing::EndsWith; |
| using ::testing::MatchesRegex; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::ReturnRef; |
| using ::testing::StrEq; |
| using ::testing::UnorderedElementsAre; |
| |
| namespace cryptohome { |
| |
| namespace { |
| |
| struct UserPassword { |
| const char* name; |
| const char* password; |
| }; |
| |
| constexpr char kUser0[] = "First User"; |
| constexpr char kUserPassword0[] = "user0_pass"; |
| |
| constexpr char kPasswordLabel[] = "password"; |
| constexpr char kAltPasswordLabel[] = "alt_password"; |
| |
| void GetKeysetBlob(const SerializedVaultKeyset& serialized, |
| brillo::SecureBlob* blob) { |
| brillo::SecureBlob local_wrapped_keyset(serialized.wrapped_keyset().length()); |
| serialized.wrapped_keyset().copy(local_wrapped_keyset.char_data(), |
| serialized.wrapped_keyset().length(), 0); |
| blob->swap(local_wrapped_keyset); |
| } |
| |
| } // namespace |
| |
| class KeysetManagementTest : public ::testing::Test { |
| public: |
| KeysetManagementTest() : crypto_(&platform_) {} |
| ~KeysetManagementTest() override {} |
| |
| // Not copyable or movable |
| KeysetManagementTest(const KeysetManagementTest&) = delete; |
| KeysetManagementTest& operator=(const KeysetManagementTest&) = delete; |
| KeysetManagementTest(KeysetManagementTest&&) = delete; |
| KeysetManagementTest& operator=(KeysetManagementTest&&) = delete; |
| |
| void SetUp() override { |
| crypto_.set_tpm(&tpm_); |
| |
| InitializeFilesystemLayout(&platform_, &crypto_, &system_salt_); |
| keyset_management_ = std::make_unique<KeysetManagement>( |
| &platform_, &crypto_, system_salt_, |
| std::make_unique<VaultKeysetFactory>()); |
| mock_vault_keyset_factory_ = new MockVaultKeysetFactory(); |
| keyset_management_mock_vk_ = std::make_unique<KeysetManagement>( |
| &platform_, &crypto_, system_salt_, |
| std::unique_ptr<VaultKeysetFactory>(mock_vault_keyset_factory_)); |
| |
| platform_.GetFake()->SetSystemSaltForLibbrillo(system_salt_); |
| |
| AddUser(kUser0, kUserPassword0); |
| |
| PrepareDirectoryStructure(); |
| } |
| |
| void TearDown() override { |
| platform_.GetFake()->RemoveSystemSaltForLibbrillo(); |
| } |
| |
| protected: |
| NiceMock<MockPlatform> platform_; |
| NiceMock<MockTpm> tpm_; |
| Crypto crypto_; |
| brillo::SecureBlob system_salt_; |
| std::unique_ptr<KeysetManagement> keyset_management_; |
| MockVaultKeysetFactory* mock_vault_keyset_factory_; |
| std::unique_ptr<KeysetManagement> keyset_management_mock_vk_; |
| |
| struct UserInfo { |
| std::string name; |
| std::string obfuscated; |
| brillo::SecureBlob passkey; |
| Credentials credentials; |
| base::FilePath homedir_path; |
| base::FilePath user_path; |
| }; |
| |
| // SETUPers |
| |
| // Information about users' keyset_management. The order of users is equal to |
| // kUsers. |
| std::vector<UserInfo> users_; |
| |
| void AddUser(const char* name, const char* password) { |
| std::string obfuscated = |
| brillo::cryptohome::home::SanitizeUserNameWithSalt(name, system_salt_); |
| brillo::SecureBlob passkey; |
| cryptohome::Crypto::PasswordToPasskey(password, system_salt_, &passkey); |
| Credentials credentials(name, passkey); |
| |
| UserInfo info = {name, |
| obfuscated, |
| passkey, |
| credentials, |
| ShadowRoot().Append(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)); |
| } |
| } |
| |
| KeyData DefaultKeyData() { |
| KeyData key_data; |
| key_data.set_label(kPasswordLabel); |
| return key_data; |
| } |
| |
| Credentials CredsForUpdate(const brillo::SecureBlob& passkey) { |
| Credentials credentials(users_[0].name, passkey); |
| KeyData key_data; |
| key_data.set_label(kAltPasswordLabel); |
| credentials.set_key_data(key_data); |
| return credentials; |
| } |
| |
| Key KeyForUpdate(const Credentials& creds, int revision) { |
| Key key; |
| std::string secret_str; |
| secret_str.resize(creds.passkey().size()); |
| secret_str.assign(reinterpret_cast<const char*>(creds.passkey().data()), |
| creds.passkey().size()); |
| key.set_secret(secret_str); |
| key.mutable_data()->set_label(creds.key_data().label()); |
| key.mutable_data()->set_revision(revision); |
| |
| return key; |
| } |
| |
| std::string SignatureForUpdate(const Key& key, |
| const std::string& signing_key) { |
| std::string changes_str; |
| ac::chrome::managedaccounts::account::Secret secret; |
| secret.set_revision(key.data().revision()); |
| secret.set_secret(key.secret()); |
| secret.SerializeToString(&changes_str); |
| |
| brillo::SecureBlob hmac_key(signing_key); |
| brillo::SecureBlob hmac_data(changes_str.begin(), changes_str.end()); |
| brillo::SecureBlob hmac = CryptoLib::HmacSha256(hmac_key, hmac_data); |
| |
| return hmac.to_string(); |
| } |
| |
| void KeysetSetUpWithKeyData(const KeyData& key_data) { |
| for (auto& user : users_) { |
| VaultKeyset vk; |
| vk.Initialize(&platform_, &crypto_); |
| vk.CreateRandom(); |
| *vk.mutable_serialized()->mutable_key_data() = key_data; |
| user.credentials.set_key_data(key_data); |
| ASSERT_TRUE(vk.Encrypt(user.passkey, user.obfuscated)); |
| ASSERT_TRUE( |
| vk.Save(user.homedir_path.Append(kKeyFile).AddExtension("0"))); |
| } |
| } |
| |
| void KeysetSetUpWithoutKeyData() { |
| for (auto& user : users_) { |
| VaultKeyset vk; |
| vk.Initialize(&platform_, &crypto_); |
| vk.CreateRandom(); |
| ASSERT_TRUE(vk.Encrypt(user.passkey, user.obfuscated)); |
| ASSERT_TRUE( |
| vk.Save(user.homedir_path.Append(kKeyFile).AddExtension("0"))); |
| } |
| } |
| |
| // TESTers |
| |
| void VerifyKeysetIndicies(const std::vector<int>& expected) { |
| std::vector<int> indicies; |
| ASSERT_TRUE( |
| keyset_management_->GetVaultKeysets(users_[0].obfuscated, &indicies)); |
| EXPECT_THAT(indicies, ContainerEq(expected)); |
| } |
| |
| void VerifyKeysetNotPresentWithCreds(const Credentials& creds) { |
| std::unique_ptr<VaultKeyset> vk = |
| keyset_management_->GetValidKeyset(creds, /* error */ nullptr); |
| ASSERT_EQ(vk.get(), nullptr); |
| } |
| |
| void VerifyKeysetPresentWithCredsAtIndex(const Credentials& creds, |
| int index) { |
| std::unique_ptr<VaultKeyset> vk = |
| keyset_management_->GetValidKeyset(creds, /* error */ nullptr); |
| ASSERT_NE(vk.get(), nullptr); |
| EXPECT_EQ(vk->legacy_index(), index); |
| EXPECT_TRUE(vk->serialized().has_wrapped_chaps_key()); |
| EXPECT_TRUE(vk->serialized().has_wrapped_reset_seed()); |
| } |
| |
| void VerifyKeysetPresentWithCredsAtIndexAndRevision(const Credentials& creds, |
| int index, |
| int revision) { |
| std::unique_ptr<VaultKeyset> vk = |
| keyset_management_->GetValidKeyset(creds, /* error */ nullptr); |
| ASSERT_NE(vk.get(), nullptr); |
| EXPECT_EQ(vk->legacy_index(), index); |
| EXPECT_EQ(vk->serialized().key_data().revision(), revision); |
| EXPECT_TRUE(vk->serialized().has_wrapped_chaps_key()); |
| EXPECT_TRUE(vk->serialized().has_wrapped_reset_seed()); |
| } |
| }; |
| |
| TEST_F(KeysetManagementTest, AreCredentialsValid) { |
| // SETUP |
| |
| KeysetSetUpWithoutKeyData(); |
| Credentials wrong_credentials(users_[0].name, brillo::SecureBlob("wrong")); |
| |
| // TEST |
| ASSERT_TRUE(keyset_management_->AreCredentialsValid(users_[0].credentials)); |
| ASSERT_FALSE(keyset_management_->AreCredentialsValid(wrong_credentials)); |
| } |
| |
| // Successfully adds initial keyset |
| TEST_F(KeysetManagementTest, AddInitialKeyset) { |
| // SETUP |
| |
| users_[0].credentials.set_key_data(DefaultKeyData()); |
| |
| // TEST |
| |
| EXPECT_TRUE(keyset_management_->AddInitialKeyset(users_[0].credentials)); |
| |
| // VERIFY |
| // Initial keyset is added, readable, has "new-er" fields correctly |
| // populated and the initial index is "0". |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| } |
| |
| // Successfully adds new keyset |
| TEST_F(KeysetManagementTest, AddKeysetSuccess) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| brillo::SecureBlob new_passkey("new pass"); |
| Credentials new_credentials(users_[0].name, new_passkey); |
| |
| // TEST |
| |
| int index = -1; |
| EXPECT_EQ(CRYPTOHOME_ERROR_NOT_SET, |
| keyset_management_->AddKeyset(users_[0].credentials, new_passkey, |
| nullptr, false, &index)); |
| EXPECT_NE(index, -1); |
| |
| // VERIFY |
| // After we add an additional keyset, we can list and read both of them. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex, index}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| VerifyKeysetPresentWithCredsAtIndex(new_credentials, index); |
| } |
| |
| // Overrides existing keyset on label collision when "clobber" flag is present. |
| TEST_F(KeysetManagementTest, AddKeysetClobberSuccess) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| brillo::SecureBlob new_passkey("new pass"); |
| Credentials new_credentials(users_[0].name, new_passkey); |
| // Re-use key data from existing credentials to cause label collision. |
| KeyData key_data = users_[0].credentials.key_data(); |
| new_credentials.set_key_data(key_data); |
| |
| // TEST |
| |
| int index = -1; |
| EXPECT_EQ(CRYPTOHOME_ERROR_NOT_SET, |
| keyset_management_->AddKeyset(users_[0].credentials, new_passkey, |
| &key_data, true, &index)); |
| EXPECT_EQ(index, 0); |
| |
| // VERIFY |
| // When adding new keyset with an "existing" label and the clobber is on, we |
| // expect it to override the keyset with the same label. Thus we shall have |
| // a keyset readable with new_credentials under the index of the old keyset. |
| // The old keyset shall be removed. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex}); |
| |
| VerifyKeysetNotPresentWithCreds(users_[0].credentials); |
| VerifyKeysetPresentWithCredsAtIndex(new_credentials, kInitialKeysetIndex); |
| } |
| |
| // Return error on label collision when no "clobber". |
| TEST_F(KeysetManagementTest, AddKeysetNoClobber) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| brillo::SecureBlob new_passkey("new pass"); |
| Credentials new_credentials(users_[0].name, new_passkey); |
| // Re-use key data from existing credentials to cause label collision. |
| KeyData key_data = users_[0].credentials.key_data(); |
| new_credentials.set_key_data(key_data); |
| |
| // TEST |
| |
| int index = -1; |
| EXPECT_EQ(CRYPTOHOME_ERROR_KEY_LABEL_EXISTS, |
| keyset_management_->AddKeyset(users_[0].credentials, new_passkey, |
| &key_data, false, &index)); |
| EXPECT_EQ(index, -1); |
| |
| // VERIFY |
| // Label collision without "clobber" causes an addition error. Old keyset |
| // shall still be readable with old credentials, and the new one shall not |
| // exist. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| VerifyKeysetNotPresentWithCreds(new_credentials); |
| } |
| |
| // Fail to add new keyset due to invalid label. |
| TEST_F(KeysetManagementTest, AddKeysetNonExistentLabel) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| brillo::SecureBlob new_passkey("new pass"); |
| Credentials new_credentials(users_[0].name, new_passkey); |
| |
| Credentials not_existing_label_credentials = users_[0].credentials; |
| KeyData key_data = users_[0].credentials.key_data(); |
| key_data.set_label("i do not exist"); |
| not_existing_label_credentials.set_key_data(key_data); |
| |
| // TEST |
| |
| int index = -1; |
| ASSERT_EQ(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_NOT_FOUND, |
| keyset_management_->AddKeyset(not_existing_label_credentials, |
| new_passkey, nullptr, false, &index)); |
| EXPECT_EQ(index, -1); |
| |
| // VERIFY |
| // Invalid label causes an addition error. Old keyset shall still be |
| // readable with old credentials, and the new one shall not exist. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| VerifyKeysetNotPresentWithCreds(new_credentials); |
| } |
| |
| // Fail to add new keyset due to invalid credentials. |
| TEST_F(KeysetManagementTest, AddKeysetInvalidCreds) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| brillo::SecureBlob new_passkey("new pass"); |
| Credentials new_credentials(users_[0].name, new_passkey); |
| |
| brillo::SecureBlob wrong_passkey("wrong"); |
| Credentials wrong_credentials(users_[0].name, wrong_passkey); |
| |
| // TEST |
| |
| int index = -1; |
| ASSERT_EQ(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED, |
| keyset_management_->AddKeyset(wrong_credentials, new_passkey, |
| nullptr, false, &index)); |
| EXPECT_EQ(index, -1); |
| |
| // VERIFY |
| // Invalid credentials cause an addition error. Old keyset shall still be |
| // readable with old credentials, and the new one shall not exist. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| VerifyKeysetNotPresentWithCreds(new_credentials); |
| } |
| |
| // Fail to add new keyset due to lacking privilieges. |
| TEST_F(KeysetManagementTest, AddKeysetInvalidPrivileges) { |
| // SETUP |
| |
| KeyData vk_key_data; |
| vk_key_data.mutable_privileges()->set_add(false); |
| |
| KeysetSetUpWithKeyData(vk_key_data); |
| |
| brillo::SecureBlob new_passkey("new pass"); |
| Credentials new_credentials(users_[0].name, new_passkey); |
| |
| // TEST |
| |
| int index = -1; |
| ASSERT_EQ(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_DENIED, |
| keyset_management_->AddKeyset(users_[0].credentials, new_passkey, |
| nullptr, false, &index)); |
| EXPECT_EQ(index, -1); |
| |
| // VERIFY |
| // Invalid permissions cause an addition error. Old keyset shall still be |
| // readable with old credentials, and the new one shall not exist. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| VerifyKeysetNotPresentWithCreds(new_credentials); |
| } |
| |
| // Fail to add new keyset due to index pool exhaustion. |
| TEST_F(KeysetManagementTest, AddKeysetNoFreeIndices) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| brillo::SecureBlob new_passkey("new pass"); |
| Credentials new_credentials(users_[0].name, new_passkey); |
| |
| // Use mock not to literally create a hundread files. |
| EXPECT_CALL(platform_, OpenFile(Property(&base::FilePath::value, |
| MatchesRegex(".*/master\\..*$")), |
| StrEq("wx"))) |
| .WillRepeatedly(Return(nullptr)); |
| |
| // TEST |
| |
| int index = -1; |
| EXPECT_EQ(CRYPTOHOME_ERROR_KEY_QUOTA_EXCEEDED, |
| keyset_management_->AddKeyset(users_[0].credentials, new_passkey, |
| nullptr, false, &index)); |
| EXPECT_EQ(index, -1); |
| |
| // VERIFY |
| // Nothing should change if we were not able to add keyset due to a lack of |
| // free slots. Since we mocked the "slot" check, we should still have only |
| // initial keyset index, adn the keyset is readable with the old credentials. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| VerifyKeysetNotPresentWithCreds(new_credentials); |
| } |
| |
| // Fail to add new keyset due to failed encryption. |
| TEST_F(KeysetManagementTest, AddKeysetEncryptFail) { |
| // SETUP |
| |
| KeysetSetUpWithoutKeyData(); |
| |
| brillo::SecureBlob new_passkey("new pass"); |
| Credentials new_credentials(users_[0].name, new_passkey); |
| |
| // Mock vk to inject encryption failure |
| auto mock_vk = new NiceMock<MockVaultKeyset>(); |
| mock_vk->mutable_serialized()->set_wrapped_reset_seed("reset_seed"); |
| EXPECT_CALL(*mock_vault_keyset_factory_, New(_, _)).WillOnce(Return(mock_vk)); |
| EXPECT_CALL(*mock_vk, Load(_)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_vk, Decrypt(_, _, _)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_vk, Encrypt(new_passkey, _)).WillOnce(Return(false)); |
| |
| // TEST |
| |
| int index = -1; |
| ASSERT_EQ(CRYPTOHOME_ERROR_BACKING_STORE_FAILURE, |
| keyset_management_mock_vk_->AddKeyset( |
| users_[0].credentials, new_passkey, nullptr, false, &index)); |
| EXPECT_EQ(index, -1); |
| |
| // VERIFY |
| // If we failed to save the added keyset due to encryption failure, the old |
| // keyset should still exist and be readable with the old credentials. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| VerifyKeysetNotPresentWithCreds(new_credentials); |
| } |
| |
| // Fail to add new keyset due to failed disk write. |
| TEST_F(KeysetManagementTest, AddKeysetSaveFail) { |
| // SETUP |
| |
| KeysetSetUpWithoutKeyData(); |
| |
| brillo::SecureBlob new_passkey("new pass"); |
| Credentials new_credentials(users_[0].name, new_passkey); |
| |
| // Mock vk to inject save failure. |
| auto mock_vk = new NiceMock<MockVaultKeyset>(); |
| mock_vk->mutable_serialized()->set_wrapped_reset_seed("reset_seed"); |
| EXPECT_CALL(*mock_vault_keyset_factory_, New(_, _)).WillOnce(Return(mock_vk)); |
| EXPECT_CALL(*mock_vk, Load(_)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_vk, Decrypt(_, _, _)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_vk, Encrypt(new_passkey, _)).WillOnce(Return(true)); |
| EXPECT_CALL(*mock_vk, Save(_)).WillOnce(Return(false)); |
| |
| // TEST |
| |
| int index = -1; |
| ASSERT_EQ(CRYPTOHOME_ERROR_BACKING_STORE_FAILURE, |
| keyset_management_mock_vk_->AddKeyset( |
| users_[0].credentials, new_passkey, nullptr, false, &index)); |
| EXPECT_EQ(index, -1); |
| |
| // VERIFY |
| // If we failed to save the added keyset due to disk failure, the old |
| // keyset should still exist and be readable with the old credentials. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| VerifyKeysetNotPresentWithCreds(new_credentials); |
| } |
| |
| // Successfully removes keyset. |
| TEST_F(KeysetManagementTest, RemoveKeysetSuccess) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| brillo::SecureBlob new_passkey("new path"); |
| Credentials new_credentials(users_[0].name, new_passkey); |
| |
| int index = -1; |
| EXPECT_EQ(CRYPTOHOME_ERROR_NOT_SET, |
| keyset_management_->AddKeyset(users_[0].credentials, new_passkey, |
| nullptr, false, &index)); |
| |
| // TEST |
| |
| EXPECT_EQ(CRYPTOHOME_ERROR_NOT_SET, |
| keyset_management_->RemoveKeyset(users_[0].credentials, |
| users_[0].credentials.key_data())); |
| |
| // VERIFY |
| // We had one initial keyset and one added one. After deleting the initial |
| // one, only the new one shoulde be available. |
| |
| VerifyKeysetIndicies({index}); |
| |
| VerifyKeysetNotPresentWithCreds(users_[0].credentials); |
| VerifyKeysetPresentWithCredsAtIndex(new_credentials, index); |
| } |
| |
| // Fails to remove due to missing the desired key. |
| TEST_F(KeysetManagementTest, RemoveKeysetNotFound) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| KeyData key_data = users_[0].credentials.key_data(); |
| key_data.set_label("i do not exist"); |
| |
| // TEST |
| |
| EXPECT_EQ(CRYPTOHOME_ERROR_KEY_NOT_FOUND, |
| keyset_management_->RemoveKeyset(users_[0].credentials, key_data)); |
| |
| // VERIFY |
| // Trying to delete keyset with non-existing label. Nothing changes, initial |
| // keyset still available with old credentials. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| } |
| |
| // Fails to remove due to not existing label. |
| TEST_F(KeysetManagementTest, RemoveKeysetNonExistentLabel) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| Credentials not_existing_label_credentials = users_[0].credentials; |
| KeyData key_data = users_[0].credentials.key_data(); |
| key_data.set_label("i do not exist"); |
| not_existing_label_credentials.set_key_data(key_data); |
| |
| // TEST |
| |
| EXPECT_EQ(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_NOT_FOUND, |
| keyset_management_->RemoveKeyset(not_existing_label_credentials, |
| users_[0].credentials.key_data())); |
| |
| // VERIFY |
| // Wrong label on authorization credentials. Nothing changes, initial |
| // keyset still available with old credentials. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| } |
| |
| // Fails to remove due to invalid credentials. |
| TEST_F(KeysetManagementTest, RemoveKeysetInvalidCreds) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| brillo::SecureBlob wrong_passkey("wrong"); |
| Credentials wrong_credentials(users_[0].name, wrong_passkey); |
| |
| // TEST |
| |
| EXPECT_EQ(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED, |
| keyset_management_->RemoveKeyset(wrong_credentials, |
| users_[0].credentials.key_data())); |
| |
| // VERIFY |
| // Wrong credentials. Nothing changes, initial keyset still available |
| // with old credentials. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| } |
| |
| // Fails to remove due to lacking privilieges. |
| TEST_F(KeysetManagementTest, RemoveKeysetInvalidPrivileges) { |
| // SETUP |
| |
| KeyData vk_key_data; |
| vk_key_data.mutable_privileges()->set_remove(false); |
| vk_key_data.set_label(kPasswordLabel); |
| |
| KeysetSetUpWithKeyData(vk_key_data); |
| |
| // TEST |
| |
| EXPECT_EQ(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_DENIED, |
| keyset_management_->RemoveKeyset(users_[0].credentials, |
| users_[0].credentials.key_data())); |
| |
| // VERIFY |
| // Wrong permission on the keyset. Nothing changes, initial keyset still |
| // available with old credentials. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| } |
| |
| // List labels. |
| TEST_F(KeysetManagementTest, GetVaultKeysetLabels) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| brillo::SecureBlob new_passkey("new path"); |
| KeyData key_data; |
| key_data.set_label(kAltPasswordLabel); |
| |
| int index = -1; |
| EXPECT_EQ(CRYPTOHOME_ERROR_NOT_SET, |
| keyset_management_->AddKeyset(users_[0].credentials, new_passkey, |
| &key_data, false, &index)); |
| |
| // TEST |
| |
| std::vector<std::string> labels; |
| EXPECT_TRUE( |
| keyset_management_->GetVaultKeysetLabels(users_[0].obfuscated, &labels)); |
| |
| // VERIFY |
| // Labels of the initial and newly added keysets are returned. |
| |
| ASSERT_EQ(2, labels.size()); |
| EXPECT_THAT(labels, UnorderedElementsAre(kPasswordLabel, kAltPasswordLabel)); |
| } |
| |
| // List labels for legacy keyset. |
| TEST_F(KeysetManagementTest, GetVaultKeysetLabelsOneLegacyLabeled) { |
| // SETUP |
| |
| KeysetSetUpWithoutKeyData(); |
| std::vector<std::string> labels; |
| |
| // TEST |
| |
| EXPECT_TRUE( |
| keyset_management_->GetVaultKeysetLabels(users_[0].obfuscated, &labels)); |
| |
| // VERIFY |
| // Initial keyset has no key data thus shall provide "legacy" label. |
| |
| ASSERT_EQ(1, labels.size()); |
| EXPECT_EQ(base::StringPrintf("%s%d", kKeyLegacyPrefix, kInitialKeysetIndex), |
| labels[0]); |
| } |
| |
| // Successfully force removes keyset. |
| TEST_F(KeysetManagementTest, ForceRemoveKeysetSuccess) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| brillo::SecureBlob new_passkey("new pass"); |
| Credentials new_credentials(users_[0].name, new_passkey); |
| brillo::SecureBlob new_passkey2("new pass2"); |
| Credentials new_credentials2(users_[0].name, new_passkey2); |
| |
| int index = -1; |
| EXPECT_EQ(CRYPTOHOME_ERROR_NOT_SET, |
| keyset_management_->AddKeyset(users_[0].credentials, new_passkey, |
| nullptr, false, &index)); |
| int index2 = -1; |
| EXPECT_EQ(CRYPTOHOME_ERROR_NOT_SET, |
| keyset_management_->AddKeyset(users_[0].credentials, new_passkey2, |
| nullptr, false, &index2)); |
| |
| // TEST |
| |
| EXPECT_TRUE( |
| keyset_management_->ForceRemoveKeyset(users_[0].obfuscated, index)); |
| // Remove a non-existing keyset is a success. |
| EXPECT_TRUE( |
| keyset_management_->ForceRemoveKeyset(users_[0].obfuscated, index)); |
| |
| // VERIFY |
| // We added two new keysets and force removed on of them. Only initial and the |
| // second added shall remain. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex, index2}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| VerifyKeysetNotPresentWithCreds(new_credentials); |
| VerifyKeysetPresentWithCredsAtIndex(new_credentials2, index2); |
| } |
| |
| // Fails to remove keyset due to invalid index. |
| TEST_F(KeysetManagementTest, ForceRemoveKeysetInvalidIndex) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| // TEST |
| |
| ASSERT_FALSE(keyset_management_->ForceRemoveKeyset(users_[0].obfuscated, -1)); |
| ASSERT_FALSE( |
| keyset_management_->ForceRemoveKeyset(users_[0].obfuscated, kKeyFileMax)); |
| |
| // VERIFY |
| // Trying to delete keyset with out-of-bound index id. Nothing changes, |
| // initial keyset still available with old creds. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| } |
| |
| // Fails to remove keyset due to injected error. |
| TEST_F(KeysetManagementTest, ForceRemoveKeysetFailedDelete) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| EXPECT_CALL(platform_, DeleteFile(Property(&base::FilePath::value, |
| EndsWith("master.0")))) |
| .WillOnce(Return(false)); |
| |
| // TEST |
| |
| ASSERT_FALSE(keyset_management_->ForceRemoveKeyset(users_[0].obfuscated, 0)); |
| |
| // VERIFY |
| // Deletion fails, Nothing changes, initial keyset still available with old |
| // creds. |
| |
| VerifyKeysetIndicies({kInitialKeysetIndex}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| } |
| |
| // Successfully moves keyset. |
| TEST_F(KeysetManagementTest, MoveKeysetSuccess) { |
| // SETUP |
| |
| constexpr int kFirstMoveIndex = 17; |
| constexpr int kSecondMoveIndex = 22; |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| // TEST |
| |
| // Move twice to test move from the initial position and from a non-initial |
| // position. |
| ASSERT_TRUE(keyset_management_->MoveKeyset( |
| users_[0].obfuscated, kInitialKeysetIndex, kFirstMoveIndex)); |
| ASSERT_TRUE(keyset_management_->MoveKeyset( |
| users_[0].obfuscated, kFirstMoveIndex, kSecondMoveIndex)); |
| |
| // VERIFY |
| // Move initial keyset twice, expect it to be accessible with old creds on the |
| // new index slot. |
| |
| VerifyKeysetIndicies({kSecondMoveIndex}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, kSecondMoveIndex); |
| } |
| |
| // Fails to move keyset. |
| TEST_F(KeysetManagementTest, MoveKeysetFail) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| brillo::SecureBlob new_passkey("new pass"); |
| Credentials new_credentials(users_[0].name, new_passkey); |
| |
| int index = -1; |
| EXPECT_EQ(CRYPTOHOME_ERROR_NOT_SET, |
| keyset_management_->AddKeyset(users_[0].credentials, new_passkey, |
| nullptr, false, &index)); |
| |
| const std::string kInitialFile = |
| base::StringPrintf("master.%d", kInitialKeysetIndex); |
| const std::string kIndexPlus2File = |
| base::StringPrintf("master.%d", index + 2); |
| const std::string kIndexPlus3File = |
| base::StringPrintf("master.%d", index + 3); |
| |
| // Inject open failure for the slot 2. |
| ON_CALL(platform_, |
| OpenFile(Property(&base::FilePath::value, EndsWith(kIndexPlus2File)), |
| StrEq("wx"))) |
| .WillByDefault(Return(nullptr)); |
| |
| // Inject rename failure for the slot 3. |
| ON_CALL(platform_, |
| Rename(Property(&base::FilePath::value, EndsWith(kInitialFile)), |
| Property(&base::FilePath::value, EndsWith(kIndexPlus3File)))) |
| .WillByDefault(Return(false)); |
| |
| // TEST |
| |
| // Out of bound indexes |
| ASSERT_FALSE(keyset_management_->MoveKeyset(users_[0].obfuscated, -1, index)); |
| ASSERT_FALSE(keyset_management_->MoveKeyset(users_[0].obfuscated, |
| kInitialKeysetIndex, -1)); |
| ASSERT_FALSE( |
| keyset_management_->MoveKeyset(users_[0].obfuscated, kKeyFileMax, index)); |
| ASSERT_FALSE(keyset_management_->MoveKeyset( |
| users_[0].obfuscated, kInitialKeysetIndex, kKeyFileMax)); |
| |
| // Not existing source |
| ASSERT_FALSE(keyset_management_->MoveKeyset(users_[0].obfuscated, index + 4, |
| index + 5)); |
| |
| // Destination exists |
| ASSERT_FALSE(keyset_management_->MoveKeyset(users_[0].obfuscated, |
| kInitialKeysetIndex, index)); |
| |
| // Destination file error-injected. |
| ASSERT_FALSE(keyset_management_->MoveKeyset(users_[0].obfuscated, |
| kInitialKeysetIndex, index + 2)); |
| ASSERT_FALSE(keyset_management_->MoveKeyset(users_[0].obfuscated, |
| kInitialKeysetIndex, index + 3)); |
| |
| // VERIFY |
| |
| // TODO(chromium:1141301, dlunev): the fact we have keyset index+3 is a bug - |
| // MoveKeyset will not cleanup created file if Rename fails. Not addressing it |
| // now durign test refactor, but will in the coming CLs. |
| VerifyKeysetIndicies({kInitialKeysetIndex, index, index + 3}); |
| |
| VerifyKeysetPresentWithCredsAtIndex(users_[0].credentials, |
| kInitialKeysetIndex); |
| VerifyKeysetPresentWithCredsAtIndex(new_credentials, index); |
| } |
| |
| TEST_F(KeysetManagementTest, ReSaveKeysetNoReSave) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| std::unique_ptr<VaultKeyset> vk0 = keyset_management_->GetValidKeyset( |
| users_[0].credentials, /* error */ nullptr); |
| ASSERT_NE(vk0.get(), nullptr); |
| |
| // TEST |
| |
| MountError code; |
| std::unique_ptr<VaultKeyset> vk_load = |
| keyset_management_->LoadUnwrappedKeyset(users_[0].credentials, &code); |
| EXPECT_EQ(MOUNT_ERROR_NONE, code); |
| |
| // VERIFY |
| |
| std::unique_ptr<VaultKeyset> vk0_new(keyset_management_->GetValidKeyset( |
| users_[0].credentials, /* error */ nullptr)); |
| ASSERT_NE(vk0_new.get(), nullptr); |
| |
| brillo::SecureBlob lhs, rhs; |
| GetKeysetBlob(vk0->serialized(), &lhs); |
| GetKeysetBlob(vk0_new->serialized(), &rhs); |
| ASSERT_EQ(lhs.size(), rhs.size()); |
| ASSERT_EQ(0, brillo::SecureMemcmp(lhs.data(), rhs.data(), lhs.size())); |
| } |
| |
| TEST_F(KeysetManagementTest, ReSaveKeysetChapsRepopulation) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| std::unique_ptr<VaultKeyset> vk0 = |
| keyset_management_->LoadVaultKeysetForUser(users_[0].obfuscated, 0); |
| ASSERT_NE(vk0.get(), nullptr); |
| vk0->mutable_serialized()->clear_wrapped_chaps_key(); |
| EXPECT_FALSE(vk0->serialized().has_wrapped_chaps_key()); |
| ASSERT_TRUE(vk0->Save(vk0->source_file())); |
| |
| // TEST |
| |
| MountError code; |
| std::unique_ptr<VaultKeyset> vk_load = |
| keyset_management_->LoadUnwrappedKeyset(users_[0].credentials, &code); |
| EXPECT_EQ(MOUNT_ERROR_NONE, code); |
| EXPECT_TRUE(vk_load->serialized().has_wrapped_chaps_key()); |
| |
| // VERIFY |
| |
| std::unique_ptr<VaultKeyset> vk0_new = keyset_management_->GetValidKeyset( |
| users_[0].credentials, /* error */ nullptr); |
| ASSERT_NE(vk0_new.get(), nullptr); |
| EXPECT_TRUE(vk0_new->serialized().has_wrapped_chaps_key()); |
| |
| ASSERT_EQ(vk0_new->chaps_key().size(), vk_load->chaps_key().size()); |
| ASSERT_EQ(0, brillo::SecureMemcmp(vk0_new->chaps_key().data(), |
| vk_load->chaps_key().data(), |
| vk0_new->chaps_key().size())); |
| } |
| |
| TEST_F(KeysetManagementTest, ReSaveOnLoadNoReSave) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| std::unique_ptr<VaultKeyset> vk0 = keyset_management_->GetValidKeyset( |
| users_[0].credentials, /* error */ nullptr); |
| ASSERT_NE(vk0.get(), nullptr); |
| |
| // TEST |
| |
| EXPECT_FALSE(keyset_management_->ShouldReSaveKeyset(vk0.get())); |
| } |
| |
| // The following tests use MOCKs for TpmState and hand-crafted vault keyset |
| // state. Ideally we shall have a fake tpm, but that is not feasible ATM. |
| |
| TEST_F(KeysetManagementTest, ReSaveOnLoadTestRegularCreds) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| std::unique_ptr<VaultKeyset> vk0 = keyset_management_->GetValidKeyset( |
| users_[0].credentials, /* error */ nullptr); |
| ASSERT_NE(vk0.get(), nullptr); |
| |
| NiceMock<MockTpmInit> mock_tpm_init; |
| EXPECT_CALL(mock_tpm_init, HasCryptohomeKey()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(mock_tpm_init, SetupTpm(true)).WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()).WillRepeatedly(Return(true)); |
| |
| crypto_.Init(&mock_tpm_init); |
| |
| // TEST |
| |
| // Scrypt wrapped shall be resaved when tpm present. |
| EXPECT_TRUE(keyset_management_->ShouldReSaveKeyset(vk0.get())); |
| |
| // Tpm wrapped not pcr bound, but no public hash - resave. |
| vk0->mutable_serialized()->set_flags(SerializedVaultKeyset::TPM_WRAPPED | |
| SerializedVaultKeyset::SCRYPT_DERIVED); |
| EXPECT_TRUE(keyset_management_->ShouldReSaveKeyset(vk0.get())); |
| |
| // Tpm wrapped pcr bound, but no public hash - resave. |
| vk0->mutable_serialized()->set_flags(SerializedVaultKeyset::TPM_WRAPPED | |
| SerializedVaultKeyset::SCRYPT_DERIVED | |
| SerializedVaultKeyset::PCR_BOUND); |
| EXPECT_TRUE(keyset_management_->ShouldReSaveKeyset(vk0.get())); |
| |
| // Tpm wrapped not pcr bound, public hash - resave. |
| vk0->mutable_serialized()->set_tpm_public_key_hash("public hash"); |
| vk0->mutable_serialized()->set_flags(SerializedVaultKeyset::TPM_WRAPPED | |
| SerializedVaultKeyset::SCRYPT_DERIVED); |
| EXPECT_TRUE(keyset_management_->ShouldReSaveKeyset(vk0.get())); |
| |
| // Tpm wrapped pcr bound, public hash - no resave. |
| vk0->mutable_serialized()->set_tpm_public_key_hash("public hash"); |
| vk0->mutable_serialized()->set_flags(SerializedVaultKeyset::TPM_WRAPPED | |
| SerializedVaultKeyset::SCRYPT_DERIVED | |
| SerializedVaultKeyset::PCR_BOUND); |
| EXPECT_FALSE(keyset_management_->ShouldReSaveKeyset(vk0.get())); |
| } |
| |
| TEST_F(KeysetManagementTest, ReSaveOnLoadTestLeCreds) { |
| // SETUP |
| |
| KeysetSetUpWithKeyData(DefaultKeyData()); |
| |
| std::unique_ptr<VaultKeyset> vk0 = keyset_management_->GetValidKeyset( |
| users_[0].credentials, /* error */ nullptr); |
| ASSERT_NE(vk0.get(), nullptr); |
| |
| NiceMock<MockTpmInit> mock_tpm_init; |
| EXPECT_CALL(mock_tpm_init, HasCryptohomeKey()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(mock_tpm_init, SetupTpm(true)).WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()).WillRepeatedly(Return(true)); |
| |
| auto le_cred_manager = new cryptohome::MockLECredentialManager(); |
| crypto_.set_le_manager_for_testing( |
| std::unique_ptr<cryptohome::LECredentialManager>(le_cred_manager)); |
| |
| crypto_.Init(&mock_tpm_init); |
| |
| // TEST |
| |
| // le credentials which doesn't need pcr binding - no re-save |
| EXPECT_CALL(*le_cred_manager, NeedsPcrBinding(_)) |
| .WillRepeatedly(Return(false)); |
| vk0->mutable_serialized()->set_flags(SerializedVaultKeyset::LE_CREDENTIAL); |
| EXPECT_FALSE(keyset_management_->ShouldReSaveKeyset(vk0.get())); |
| |
| // le credentials which needs pcr binding - no resave. |
| EXPECT_CALL(*le_cred_manager, NeedsPcrBinding(_)) |
| .WillRepeatedly(Return(true)); |
| vk0->mutable_serialized()->set_flags(SerializedVaultKeyset::LE_CREDENTIAL); |
| EXPECT_TRUE(keyset_management_->ShouldReSaveKeyset(vk0.get())); |
| } |
| |
| TEST_F(KeysetManagementTest, RemoveLECredentials) { |
| // TODO(dlunev): this tests nothing really, re-write the test to actually do |
| // functionality test. |
| keyset_management_->RemoveLECredentials(users_[0].obfuscated); |
| } |
| |
| } // namespace cryptohome |