| // Copyright (c) 2013 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. |
| |
| // Unit tests for Mount. |
| |
| #include "cryptohome/mount.h" |
| |
| #include <memory> |
| #include <openssl/evp.h> |
| #include <openssl/sha.h> |
| #include <pwd.h> |
| #include <stdlib.h> |
| #include <string.h> // For memset(), memcpy() |
| #include <sys/types.h> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/bind_helpers.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/time/time.h> |
| #include <brillo/cryptohome.h> |
| #include <brillo/process/process_mock.h> |
| #include <brillo/secure_blob.h> |
| #include <chromeos/constants/cryptohome.h> |
| #include <gtest/gtest.h> |
| #include <policy/libpolicy.h> |
| #include <policy/mock_device_policy.h> |
| |
| #include "cryptohome/bootlockbox/mock_boot_lockbox.h" |
| #include "cryptohome/credentials.h" |
| #include "cryptohome/crypto.h" |
| #include "cryptohome/cryptohome_common.h" |
| #include "cryptohome/cryptolib.h" |
| #include "cryptohome/fake_le_credential_backend.h" |
| #include "cryptohome/homedirs.h" |
| #include "cryptohome/make_tests.h" |
| #include "cryptohome/mock_chaps_client_factory.h" |
| #include "cryptohome/mock_crypto.h" |
| #include "cryptohome/mock_homedirs.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_user_session.h" |
| #include "cryptohome/mock_vault_keyset.h" |
| #include "cryptohome/mount_helper.h" |
| #include "cryptohome/user_oldest_activity_timestamp_cache.h" |
| #include "cryptohome/vault_keyset.h" |
| |
| #include "timestamp.pb.h" // NOLINT(build/include) |
| #include "vault_keyset.pb.h" // NOLINT(build/include) |
| |
| using brillo::SecureBlob; |
| using base::FilePath; |
| using ::testing::AllOf; |
| using ::testing::AnyNumber; |
| using ::testing::AnyOf; |
| using ::testing::DoAll; |
| using ::testing::EndsWith; |
| using ::testing::InSequence; |
| using ::testing::Invoke; |
| using ::testing::Mock; |
| using ::testing::NiceMock; |
| using ::testing::Not; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::SetArgPointee; |
| using ::testing::StartsWith; |
| using ::testing::StrEq; |
| using ::testing::StrictMock; |
| using ::testing::Unused; |
| using ::testing::WithArgs; |
| using ::testing::_; |
| |
| namespace { |
| |
| const FilePath kImageDir("test_image_dir"); |
| const FilePath kImageSaltFile = kImageDir.Append("salt"); |
| const FilePath kSkelDir = kImageDir.Append("skel"); |
| const FilePath kLoopDevice("/dev/loop7"); |
| |
| const gid_t kDaemonGid = 400; // TODO(wad): expose this in mount.h |
| const int kPinUserIndex = 14; |
| const char kHexHeSecret[] = |
| "F3D9D5B126C36676689E18BB8517D95DF4F30947E71D4A840824425760B1D3FA"; |
| const char kHexResetSecret[] = |
| "B133D2450392335BA8D33AA95AD52488254070C66F5D79AEA1A46AC4A30760D4"; |
| const char kHexWrappedKeyset[] = |
| "B737B5D73E39BD390A4F361CE2FC166CF1E89EC6AEAA35D4B34456502C48B4F5EFA310077"\ |
| "324B393E13AF633DF3072FF2EC78BD2B80D919035DB97C30F1AD418737DA3F26A4D35DF6B"\ |
| "6A9743BD0DF3D37D8A68DE0932A9905452D05ECF92701B9805937F76EE01D10924268F057"\ |
| "EDD66087774BB86C2CB92B01BD3A3C41C10C52838BD3A3296474598418E5191DEE9E8D831"\ |
| "3C859C9EDB0D5F2BC1D7FC3C108A0D4ABB2D90E413086BCFFD0902AB68E2BF787817EB10C"\ |
| "25E2E43011CAB3FB8AA"; |
| const char kHexSalt[] = "D470B9B108902241"; |
| const char kHexVaultKey[] = |
| "665A58534E684F2B61516B6D42624B514E6749732B4348427450305453754158377232347"\ |
| "37A79466C6B383D"; |
| const char kHexFekIv[] = "EA80F14BF29C6D580D536E7F0CC47F3E"; |
| const char kHexChapsIv[] = "ED85D928940E5B02ED218F29225AA34F"; |
| const char kHexWrappedChapsKey[] = |
| "7D7D01EECC8DAE7906CAD56310954BBEB3CC81765210D29902AB92DDE074217771AD284F2"\ |
| "12C13897C6CBB30CEC4CD75"; |
| |
| } // namespace |
| |
| namespace cryptohome { |
| |
| ACTION_P2(SetOwner, owner_known, owner) { |
| if (owner_known) |
| *arg0 = owner; |
| return owner_known; |
| } |
| |
| ACTION_P(SetEphemeralUsersEnabled, ephemeral_users_enabled) { |
| *arg0 = ephemeral_users_enabled; |
| return true; |
| } |
| |
| // Straight pass through. |
| Tpm::TpmRetryAction TpmPassthroughSealWithAuthorization( |
| uint32_t _key, const SecureBlob &plaintext, Unused, Unused, |
| SecureBlob *ciphertext) { |
| ciphertext->resize(plaintext.size()); |
| memcpy(ciphertext->data(), plaintext.data(), plaintext.size()); |
| return Tpm::kTpmRetryNone; |
| } |
| |
| Tpm::TpmRetryAction TpmPassthroughDecrypt(uint32_t _key, |
| const SecureBlob &ciphertext, |
| Unused, |
| Unused, |
| SecureBlob *plaintext) { |
| plaintext->resize(ciphertext.size()); |
| memcpy(plaintext->data(), ciphertext.data(), ciphertext.size()); |
| return Tpm::kTpmRetryNone; |
| } |
| |
| std::string HexDecode(const std::string& hex) { |
| std::vector<uint8_t> output; |
| CHECK(base::HexStringToBytes(hex, &output)); |
| return std::string(output.begin(), output.end()); |
| } |
| |
| class MountTest |
| : public ::testing::TestWithParam<bool /* should_test_ecryptfs */> { |
| public: |
| MountTest() : crypto_(&platform_) { } |
| virtual ~MountTest() { } |
| |
| void SetUp() { |
| // Populate the system salt |
| helper_.SetUpSystemSalt(); |
| helper_.InjectSystemSalt(&platform_, kImageSaltFile); |
| |
| // Set up default uid/gid values |
| chronos_uid_ = 1000; |
| chronos_gid_ = 1000; |
| shared_gid_ = 1001; |
| chaps_uid_ = 223; |
| |
| crypto_.set_tpm(&tpm_); |
| crypto_.set_use_tpm(false); |
| |
| user_timestamp_cache_.reset(new UserOldestActivityTimestampCache()); |
| mount_ = new Mount(); |
| mount_->set_homedirs(&homedirs_); |
| mount_->set_use_tpm(false); |
| mount_->set_shadow_root(kImageDir); |
| mount_->set_skel_source(kSkelDir); |
| mount_->set_chaps_client_factory(&chaps_client_factory_); |
| // Perform mounts in-process. |
| mount_->set_mount_guest_session_out_of_process(false); |
| mount_->set_mount_guest_session_non_root_namespace(false); |
| homedirs_.set_crypto(&crypto_); |
| homedirs_.set_platform(&platform_); |
| homedirs_.set_shadow_root(kImageDir); |
| EXPECT_TRUE(homedirs_.GetSystemSalt(nullptr /* blob */)); |
| set_policy(false, "", false); |
| premount_callback_counter_ = 0; |
| } |
| |
| void TearDown() { |
| mount_ = NULL; |
| helper_.TearDownSystemSalt(); |
| } |
| |
| void InsertTestUsers(const TestUserInfo* user_info_list, int count) { |
| helper_.InitTestData(kImageDir, user_info_list, |
| static_cast<size_t>(count), ShouldTestEcryptfs()); |
| } |
| |
| void PreMountCallback() { |
| premount_callback_counter_ += 1; |
| } |
| |
| bool DoMountInit() { |
| EXPECT_CALL(platform_, GetUserId("chronos", _, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(chronos_uid_), |
| SetArgPointee<2>(chronos_gid_), |
| Return(true))); |
| EXPECT_CALL(platform_, GetUserId("chaps", _, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(chaps_uid_), |
| SetArgPointee<2>(shared_gid_), |
| Return(true))); |
| EXPECT_CALL(platform_, GetGroupId("chronos-access", _)) |
| .WillOnce(DoAll(SetArgPointee<1>(shared_gid_), |
| Return(true))); |
| return mount_->Init(&platform_, &crypto_, |
| user_timestamp_cache_.get(), |
| base::BindRepeating(&MountTest::PreMountCallback, |
| base::Unretained(this))); |
| } |
| |
| bool LoadSerializedKeyset(const brillo::Blob& contents, |
| cryptohome::SerializedVaultKeyset* serialized) { |
| CHECK_NE(contents.size(), 0U); |
| return serialized->ParseFromArray(contents.data(), contents.size()); |
| } |
| |
| bool StoreSerializedKeyset(const SerializedVaultKeyset& serialized, |
| TestUser *user) { |
| user->credentials.resize(serialized.ByteSizeLong()); |
| serialized.SerializeWithCachedSizesToArray( |
| static_cast<google::protobuf::uint8*>(&user->credentials[0])); |
| return true; |
| } |
| |
| void GetKeysetBlob(const SerializedVaultKeyset& serialized, |
| SecureBlob* blob) { |
| 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); |
| } |
| |
| void set_policy(bool owner_known, |
| const std::string& owner, |
| bool ephemeral_users_enabled) { |
| policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy(); |
| EXPECT_CALL(*device_policy, LoadPolicy()) |
| .Times(AnyNumber()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(*device_policy, GetOwner(_)) |
| .WillRepeatedly(SetOwner(owner_known, owner)); |
| EXPECT_CALL(*device_policy, GetEphemeralUsersEnabled(_)) |
| .WillRepeatedly(SetEphemeralUsersEnabled(ephemeral_users_enabled)); |
| mount_->set_policy_provider(new policy::PolicyProvider( |
| std::unique_ptr<policy::MockDevicePolicy>(device_policy))); |
| } |
| |
| // Returns true if the test is running for eCryptfs, false if for dircrypto. |
| bool ShouldTestEcryptfs() const { return GetParam(); } |
| |
| Mount::MountArgs GetDefaultMountArgs() const { |
| Mount::MountArgs args; |
| args.create_as_ecryptfs = ShouldTestEcryptfs(); |
| return args; |
| } |
| |
| bool SetUserAsLECredential(TestUser* user) { |
| SerializedVaultKeyset serialized; |
| if (!LoadSerializedKeyset(user->credentials, &serialized)) { |
| LOG(ERROR) << "Failed to parse keyset for " << user->username; |
| return false; |
| } |
| serialized.set_flags(SerializedVaultKeyset::TPM_WRAPPED | |
| SerializedVaultKeyset::LE_CREDENTIAL); |
| serialized.set_le_fek_iv(HexDecode(kHexFekIv)); |
| serialized.set_le_chaps_iv(HexDecode(kHexChapsIv)); |
| serialized.set_wrapped_keyset(HexDecode(kHexWrappedKeyset)); |
| serialized.set_wrapped_chaps_key(HexDecode(kHexWrappedChapsKey)); |
| serialized.set_salt(HexDecode(kHexSalt)); |
| if (!StoreSerializedKeyset(serialized, user)) { |
| LOG(ERROR) << "Failed to serialize new timestamp'd keyset for " |
| << user->username; |
| return false; |
| } |
| return true; |
| } |
| |
| void InitializeLECredential() { |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| mount_->set_use_tpm(true); |
| crypto_.set_use_tpm(true); |
| |
| EXPECT_CALL(tpm_init_, HasCryptohomeKey()) |
| .WillOnce(Return(false)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_init_, SetupTpm(true)) |
| .WillOnce(Return(true)) // This is by crypto.Init() and |
| .WillOnce(Return(true)); // because HasCryptohomeKey returned false once. |
| |
| EXPECT_CALL(tpm_, IsEnabled()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(platform_, CreateDirectory(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| |
| le_cred_manager_ = new cryptohome::MockLECredentialManager(); |
| EXPECT_CALL(*le_cred_manager_, CheckCredential(_, _, _, _)) |
| .WillRepeatedly( |
| DoAll(SetArgPointee<2>(SecureBlob(HexDecode(kHexHeSecret))), |
| SetArgPointee<3>(SecureBlob(HexDecode(kHexResetSecret))), |
| Return(LE_CRED_SUCCESS))); |
| crypto_.set_le_manager_for_testing( |
| std::unique_ptr<cryptohome::LECredentialManager>(le_cred_manager_)); |
| |
| crypto_.Init(&tpm_init_); |
| |
| InsertTestUsers(&kDefaultUsers[kPinUserIndex], 1); |
| pin_user_ = &helper_.users[0]; |
| pin_credentials_ = std::make_unique<Credentials>( |
| pin_user_->username, SecureBlob(HexDecode(kHexVaultKey))); |
| KeyData pin_label; |
| pin_label.set_label("PIN"); |
| pin_credentials_->set_key_data(pin_label); |
| |
| pin_user_->InjectKeyset(&platform_, true); |
| SetUserAsLECredential(pin_user_); |
| EXPECT_CALL(platform_, ReadFile(pin_user_->keyset_path, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(pin_user_->credentials), |
| Return(true))); |
| } |
| |
| // Sets expectations for cryptohome key setup. |
| void ExpectCryptohomeKeySetup(const TestUser& user) { |
| if (ShouldTestEcryptfs()) { |
| ExpectCryptohomeKeySetupForEcryptfs(user); |
| } else { |
| ExpectCryptohomeKeySetupForDircrypto(user); |
| } |
| } |
| |
| // Sets expectations for cryptohome key setup for ecryptfs. |
| void ExpectCryptohomeKeySetupForEcryptfs(const TestUser& user) { |
| EXPECT_CALL(platform_, AddEcryptfsAuthToken(_, _, _)) |
| .Times(2) |
| .WillRepeatedly(Return(true)); |
| } |
| |
| // Sets expectations for cryptohome key setup for dircrypto. |
| void ExpectCryptohomeKeySetupForDircrypto(const TestUser& user) { |
| const key_serial_t kDirCryptoKeyId = 12345; |
| EXPECT_CALL(platform_, AddDirCryptoKeyToKeyring(_, _, _)) |
| .WillOnce(DoAll(SetArgPointee<2>(kDirCryptoKeyId), Return(true))); |
| EXPECT_CALL(platform_, SetDirCryptoKey(user.vault_mount_path, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, InvalidateDirCryptoKey(kDirCryptoKeyId, kImageDir)) |
| .WillRepeatedly(Return(true)); |
| } |
| |
| void ExpectCryptohomeMountShadowOnly(const TestUser& user) { |
| ExpectCryptohomeKeySetup(user); |
| if (ShouldTestEcryptfs()) { |
| EXPECT_CALL(platform_, |
| Mount(user.vault_path, user.vault_mount_path, |
| "ecryptfs", kDefaultMountFlags, _)) |
| .WillOnce(Return(true)); |
| } |
| EXPECT_CALL(platform_, CreateDirectory(user.vault_mount_path)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, IsDirectoryMounted(user.vault_mount_path)) |
| .WillOnce(Return(false)); |
| } |
| |
| // Sets expectations for cryptohome mount. |
| void ExpectCryptohomeMount(const TestUser& user) { |
| ExpectCryptohomeKeySetup(user); |
| ExpectDaemonStoreMounts(user, false /* ephemeral_mount */); |
| if (ShouldTestEcryptfs()) { |
| EXPECT_CALL(platform_, Mount(user.vault_path, user.vault_mount_path, |
| "ecryptfs", kDefaultMountFlags, _)) |
| .WillOnce(Return(true)); |
| } |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, CreateDirectory(user.vault_mount_path)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| CreateDirectory(MountHelper::GetNewUserPath(user.username))) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(platform_, IsDirectoryMounted(user.vault_mount_path)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, IsDirectoryMounted(FilePath("/home/chronos/user"))) |
| .WillOnce(Return(false)); |
| |
| EXPECT_CALL(platform_, Bind(user.user_vault_mount_path, |
| user.user_mount_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, Bind(user.user_vault_mount_path, |
| user.legacy_user_mount_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, Bind(user.user_vault_mount_path, |
| MountHelper::GetNewUserPath(user.username))) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, Bind(user.root_vault_mount_path, |
| user.root_mount_path)) |
| .WillOnce(Return(true)); |
| ExpectDownloadsBindMounts(user); |
| EXPECT_CALL(platform_, |
| RestoreSELinuxContexts( |
| base::FilePath(user.vault_mount_path), |
| true)) |
| .WillOnce(Return(true)); |
| } |
| |
| void ExpectDownloadsBindMounts(const TestUser& user) { |
| // Mounting Downloads to MyFiles/Downloads in: |
| // - /home/chronos/u-<hash> |
| // - /home/user/<hash> |
| // - /home/chronos/user |
| FilePath user_dirs[] = { |
| MountHelper::GetNewUserPath(user.username), |
| brillo::cryptohome::home::GetUserPath(user.username), |
| FilePath("/home/chronos/user"), |
| }; |
| |
| EXPECT_CALL( |
| platform_, |
| IsDirectoryMounted(FilePath("/home/chronos/user/MyFiles/Downloads"))) |
| .WillOnce(Return(false)); |
| for (auto const& home : user_dirs) { |
| auto downloads_path = home.Append("Downloads"); |
| auto downloads_in_myfiles = home.Append("MyFiles").Append("Downloads"); |
| |
| EXPECT_CALL(platform_, DirectoryExists(home)).WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(downloads_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(downloads_in_myfiles)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, Bind(downloads_path, downloads_in_myfiles)) |
| .WillOnce(Return(true)); |
| } |
| } |
| |
| void ExpectDownloadsUnmounts(const TestUser& user) { |
| // Mounting Downloads to MyFiles/Downloads in: |
| // - /home/chronos/u-<hash> |
| // - /home/user/<hash> |
| // - /home/chronos/user |
| FilePath user_dirs[] = { |
| MountHelper::GetNewUserPath(user.username), |
| brillo::cryptohome::home::GetUserPath(user.username), |
| FilePath("/home/chronos/user"), |
| }; |
| for (auto const& home : user_dirs) { |
| EXPECT_CALL(platform_, |
| Unmount(home.Append("MyFiles").Append("Downloads"), _, _)) |
| .WillOnce(Return(true)); |
| } |
| } |
| |
| void ExpectEphemeralCryptohomeMount(const TestUser& user) { |
| EXPECT_CALL(platform_, StatVFS(FilePath(kEphemeralCryptohomeDir), _)) |
| .WillOnce(Return(true)); |
| const FilePath ephemeral_filename = |
| MountHelper::GetEphemeralSparseFile(user.obfuscated_username); |
| EXPECT_CALL(platform_, CreateSparseFile(ephemeral_filename, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, AttachLoop(ephemeral_filename)) |
| .WillOnce(Return(kLoopDevice)); |
| EXPECT_CALL(platform_, FormatExt4(ephemeral_filename, |
| kDefaultExt4FormatOpts, 0)) |
| .WillOnce(Return(true)); |
| |
| EXPECT_CALL(platform_, |
| Mount(kLoopDevice, _, kEphemeralMountType, |
| kDefaultMountFlags, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, IsDirectoryMounted(FilePath("/home/chronos/user"))) |
| .WillOnce(Return(false)); // first mount |
| EXPECT_CALL( |
| platform_, |
| IsDirectoryMounted(FilePath("/home/chronos/user/MyFiles/Downloads"))) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, Bind(_, _)) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(platform_, GetFileEnumerator(kSkelDir, _, _)) |
| .WillOnce(Return(new NiceMock<MockFileEnumerator>())) |
| .WillOnce(Return(new NiceMock<MockFileEnumerator>())); |
| EXPECT_CALL(platform_, DirectoryExists(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, CreateDirectory(user.vault_path)) |
| .Times(0); |
| EXPECT_CALL(platform_, CreateDirectory(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetOwnership(_, _, _, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetPermissions(_, _)).WillRepeatedly(Return(true)); |
| ExpectDaemonStoreMounts(user, true /* ephemeral_mount */); |
| } |
| |
| // Sets expectations for MountHelper::MountDaemonStoreDirectories. In |
| // particular, sets up |platform_| to pretend that all daemon store |
| // directories exists, so that they're all mounted. Without calling this |
| // method, daemon store directories are pretended to not exist. |
| void ExpectDaemonStoreMounts(const TestUser& user, bool ephemeral_mount) { |
| // Return a mock daemon store directory in /etc/daemon-store. |
| constexpr char kDaemonName[] = "mock-daemon"; |
| constexpr uid_t kDaemonUid = 123; |
| constexpr gid_t kDaemonGid = 234; |
| struct stat stat_data = {}; |
| stat_data.st_mode = S_IFDIR; |
| stat_data.st_uid = kDaemonUid; |
| stat_data.st_gid = kDaemonGid; |
| const base::FilePath daemon_store_base_dir(kEtcDaemonStoreBaseDir); |
| const FileEnumerator::FileInfo daemon_info( |
| daemon_store_base_dir.AppendASCII(kDaemonName), stat_data); |
| NiceMock<MockFileEnumerator>* daemon_enumerator = |
| new NiceMock<MockFileEnumerator>(); |
| daemon_enumerator->entries_.push_back(daemon_info); |
| EXPECT_CALL(platform_, GetFileEnumerator(daemon_store_base_dir, false, |
| base::FileEnumerator::DIRECTORIES)) |
| .WillOnce(Return(daemon_enumerator)); |
| |
| const FilePath run_daemon_store_path = |
| FilePath(kRunDaemonStoreBaseDir).Append(kDaemonName); |
| |
| EXPECT_CALL(platform_, DirectoryExists(run_daemon_store_path)) |
| .WillOnce(Return(true)); |
| |
| const FilePath root_home = ephemeral_mount ? user.root_ephemeral_mount_path |
| : user.root_vault_mount_path; |
| const FilePath mount_source = root_home.Append(kDaemonName); |
| const FilePath mount_target = |
| run_daemon_store_path.Append(user.obfuscated_username); |
| |
| EXPECT_CALL(platform_, CreateDirectory(mount_source)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, CreateDirectory(mount_target)) |
| .WillOnce(Return(true)); |
| |
| EXPECT_CALL(platform_, SetOwnership(mount_source, stat_data.st_uid, |
| stat_data.st_gid, false)) |
| .WillOnce(Return(true)); |
| |
| EXPECT_CALL(platform_, SetPermissions(mount_source, stat_data.st_mode)) |
| .WillOnce(Return(true)); |
| |
| EXPECT_CALL(platform_, Bind(mount_source, mount_target)) |
| .WillOnce(Return(true)); |
| } |
| |
| void ExpectCryptohomeRemoval(const TestUser& user) { |
| EXPECT_CALL(platform_, DeleteFile(user.base_path, true)).Times(1); |
| EXPECT_CALL(platform_, DeleteFile(user.user_mount_path, true)).Times(1); |
| EXPECT_CALL(platform_, DeleteFile(user.root_mount_path, true)).Times(1); |
| } |
| |
| protected: |
| // Protected for trivial access. |
| MakeTests helper_; |
| uid_t chronos_uid_; |
| gid_t chronos_gid_; |
| uid_t chaps_uid_; |
| gid_t shared_gid_; |
| NiceMock<MockPlatform> platform_; |
| NiceMock<MockTpm> tpm_; |
| NiceMock<MockTpmInit> tpm_init_; |
| Crypto crypto_; |
| HomeDirs homedirs_; |
| FakeLECredentialBackend le_cred_backend_; |
| cryptohome::MockLECredentialManager* le_cred_manager_; |
| TestUser* pin_user_; |
| std::unique_ptr<Credentials> pin_credentials_; |
| MockChapsClientFactory chaps_client_factory_; |
| std::unique_ptr<UserOldestActivityTimestampCache> user_timestamp_cache_; |
| scoped_refptr<Mount> mount_; |
| int premount_callback_counter_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MountTest); |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(WithEcryptfs, MountTest, ::testing::Values(true)); |
| INSTANTIATE_TEST_SUITE_P(WithDircrypto, MountTest, ::testing::Values(false)); |
| |
| TEST_P(MountTest, BadInitTest) { |
| // Create a Mount instance that points to a bad shadow root. |
| mount_->set_shadow_root(FilePath("/dev/null")); |
| |
| SecureBlob passkey; |
| cryptohome::Crypto::PasswordToPasskey(kDefaultUsers[0].password, |
| helper_.system_salt, &passkey); |
| Credentials credentials(kDefaultUsers[0].username, passkey); |
| |
| // Shadow root creation should fail. |
| EXPECT_CALL(platform_, DirectoryExists(FilePath("/dev/null"))) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, CreateDirectory(FilePath("/dev/null"))) |
| .WillRepeatedly(Return(false)); |
| // Salt creation failure because shadow_root is bogus. |
| EXPECT_CALL(platform_, FileExists(FilePath("/dev/null/salt"))) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, |
| WriteSecureBlobToFileAtomicDurable( |
| FilePath("/dev/null/salt"), _, _)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, GetUserId("chronos", _, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(1000), SetArgPointee<2>(1000), |
| Return(true))); |
| EXPECT_CALL(platform_, GetUserId("chaps", _, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(1001), SetArgPointee<2>(1001), |
| Return(true))); |
| EXPECT_CALL(platform_, GetGroupId("chronos-access", _)) |
| .WillOnce(DoAll(SetArgPointee<1>(1002), Return(true))); |
| EXPECT_FALSE( |
| mount_->Init(&platform_, &crypto_, user_timestamp_cache_.get(), |
| base::DoNothing())); |
| ASSERT_FALSE(mount_->AreValid(credentials)); |
| } |
| |
| TEST_P(MountTest, NamespaceCreationPass) { |
| mount_->set_mount_guest_session_non_root_namespace(true); |
| brillo::ProcessMock* mock_process = platform_.mock_process(); |
| EXPECT_CALL(*mock_process, Run()).WillOnce(Return(0)); |
| EXPECT_TRUE(mount_->Init(&platform_, &crypto_, user_timestamp_cache_.get(), |
| base::DoNothing())); |
| } |
| |
| TEST_P(MountTest, NamespaceCreationFail) { |
| mount_->set_mount_guest_session_non_root_namespace(true); |
| brillo::ProcessMock* mock_process = platform_.mock_process(); |
| EXPECT_CALL(*mock_process, Run()).WillOnce(Return(1)); |
| EXPECT_FALSE(mount_->Init(&platform_, &crypto_, user_timestamp_cache_.get(), |
| base::DoNothing())); |
| } |
| |
| TEST_P(MountTest, CurrentCredentialsTest) { |
| // Create a Mount instance that points to a good shadow root, test that it |
| // properly authenticates against the first key. |
| SecureBlob passkey; |
| cryptohome::Crypto::PasswordToPasskey(kDefaultUsers[3].password, |
| helper_.system_salt, &passkey); |
| Credentials credentials(kDefaultUsers[3].username, passkey); |
| |
| EXPECT_TRUE(DoMountInit()); |
| |
| NiceMock<MockUserSession> user_session; |
| user_session.Init(SecureBlob()); |
| user_session.SetUser(credentials); |
| mount_->set_current_user(&user_session); |
| |
| EXPECT_CALL(user_session, CheckUser(_)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(user_session, Verify(_)) |
| .WillOnce(Return(true)); |
| |
| ASSERT_TRUE(mount_->AreValid(credentials)); |
| } |
| |
| MATCHER_P(CredentialsEqual, credentials, "") { |
| const Credentials& expected_creds = credentials; |
| return expected_creds.username() == arg.username() && |
| expected_creds.passkey() == arg.passkey(); |
| } |
| |
| TEST_P(MountTest, PropagateCredentialsToUser) { |
| SecureBlob passkey; |
| cryptohome::Crypto::PasswordToPasskey(kDefaultUsers[3].password, |
| helper_.system_salt, &passkey); |
| Credentials up(kDefaultUsers[3].username, passkey); |
| |
| const int key_index = 2; |
| |
| NiceMock<MockUserSession> user_session; |
| mount_->set_current_user(&user_session); |
| |
| EXPECT_CALL(user_session, SetUser(CredentialsEqual(testing::ByRef(up)))) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(user_session, set_key_index(key_index)); |
| ASSERT_TRUE(mount_->SetUserCreds(up, key_index)); |
| } |
| |
| TEST_P(MountTest, BadDecryptTest) { |
| // Create a Mount instance that points to a good shadow root, test that it |
| // properly denies access with a bad passkey. |
| SecureBlob passkey; |
| cryptohome::Crypto::PasswordToPasskey("bogus", helper_.system_salt, &passkey); |
| Credentials credentials(kDefaultUsers[4].username, passkey); |
| |
| EXPECT_TRUE(DoMountInit()); |
| ASSERT_FALSE(mount_->AreValid(credentials)); |
| } |
| |
| TEST_P(MountTest, MountCryptohomeNoPrivileges) { |
| // Check that Mount only works if the mount permission is given. |
| InsertTestUsers(&kDefaultUsers[10], 1); |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| TestUser *user = &helper_.users[0]; |
| user->key_data.set_label("my key!"); |
| user->use_key_data = true; |
| user->key_data.mutable_privileges()->set_mount(false); |
| // Regenerate the serialized vault keyset. |
| user->GenerateCredentials(ShouldTestEcryptfs()); |
| Credentials credentials(user->username, user->passkey); |
| // Let the legacy key iteration work here. |
| |
| user->InjectUserPaths(&platform_, chronos_uid_, chronos_gid_, shared_gid_, |
| kDaemonGid, ShouldTestEcryptfs()); |
| user->InjectKeyset(&platform_, true); |
| |
| if (ShouldTestEcryptfs()) { |
| EXPECT_CALL(platform_, ClearUserKeyring()) |
| .WillOnce(Return(true)); |
| } |
| |
| EXPECT_CALL(platform_, CreateDirectory(user->vault_mount_path)) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(platform_, |
| CreateDirectory(MountHelper::GetNewUserPath(user->username))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| |
| EXPECT_CALL(platform_, RestoreSELinuxContexts(_, _)).Times(0); |
| |
| MountError error = MOUNT_ERROR_NONE; |
| EXPECT_FALSE( |
| mount_->MountCryptohome(credentials, GetDefaultMountArgs(), &error)); |
| EXPECT_EQ(MOUNT_ERROR_UNPRIVILEGED_KEY, error); |
| } |
| |
| TEST_P(MountTest, MountCryptohomeHasPrivileges) { |
| // Check that Mount only works if the mount permission is given. |
| InsertTestUsers(&kDefaultUsers[10], 1); |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| TestUser *user = &helper_.users[0]; |
| user->key_data.set_label("my key!"); |
| user->use_key_data = true; |
| user->key_data.mutable_privileges()->set_mount(true); |
| // Regenerate the serialized vault keyset. |
| user->GenerateCredentials(ShouldTestEcryptfs()); |
| Credentials credentials(user->username, user->passkey); |
| // Let the legacy key iteration work here. |
| |
| user->InjectUserPaths(&platform_, chronos_uid_, chronos_gid_, shared_gid_, |
| kDaemonGid, ShouldTestEcryptfs()); |
| user->InjectKeyset(&platform_, true); |
| |
| ExpectCryptohomeMount(*user); |
| EXPECT_CALL(platform_, ClearUserKeyring()) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| |
| // user exists, so there'll be no skel copy after. |
| |
| MountError error = MOUNT_ERROR_NONE; |
| ASSERT_TRUE( |
| mount_->MountCryptohome(credentials, GetDefaultMountArgs(), &error)); |
| |
| EXPECT_CALL(platform_, Unmount(_, _, _)).WillRepeatedly(Return(true)); |
| |
| // Unmount here to avoid the scoped Mount doing it implicitly. |
| EXPECT_CALL(platform_, GetCurrentTime()) |
| .WillOnce(Return(base::Time::Now())); |
| EXPECT_CALL(platform_, |
| WriteStringToFileAtomicDurable(user->timestamp_path, _, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, ClearUserKeyring()) |
| .WillOnce(Return(true)); |
| EXPECT_TRUE(mount_->UnmountCryptohome()); |
| } |
| |
| TEST_P(MountTest, BindMyFilesDownloadsSuccess) { |
| FilePath dest_dir("/home/chronos/u-userhash"); |
| auto downloads_path = dest_dir.Append("Downloads"); |
| auto downloads_in_myfiles = dest_dir.Append("MyFiles").Append("Downloads"); |
| |
| // All directories must exist for bind mount succeed. |
| EXPECT_CALL(platform_, DirectoryExists(dest_dir)).WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(downloads_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(downloads_in_myfiles)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, Bind(downloads_path, downloads_in_myfiles)) |
| .WillOnce(Return(true)); |
| |
| MountHelper mnt_helper(chronos_uid_, chronos_gid_, shared_gid_, kImageDir, |
| kSkelDir, helper_.system_salt, true /*legacy_mount*/, |
| &platform_); |
| |
| EXPECT_TRUE(mnt_helper.BindMyFilesDownloads(dest_dir)); |
| } |
| |
| TEST_P(MountTest, BindMyFilesDownloadsMissingUserHome) { |
| FilePath dest_dir("/home/chronos/u-userhash"); |
| |
| // When dest_dir doesn't exists BindMyFilesDownloads returns false. |
| EXPECT_CALL(platform_, DirectoryExists(dest_dir)).WillOnce(Return(false)); |
| |
| MountHelper mnt_helper(chronos_uid_, chronos_gid_, shared_gid_, kImageDir, |
| kSkelDir, helper_.system_salt, true /*legacy_mount*/, |
| &platform_); |
| |
| EXPECT_FALSE(mnt_helper.BindMyFilesDownloads(dest_dir)); |
| } |
| |
| TEST_P(MountTest, BindMyFilesDownloadsMissingDownloads) { |
| FilePath dest_dir("/home/chronos/u-userhash"); |
| auto downloads_path = dest_dir.Append("Downloads"); |
| |
| // When Downloads doesn't exists BindMyFilesDownloads returns false. |
| EXPECT_CALL(platform_, DirectoryExists(dest_dir)).WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(downloads_path)) |
| .WillOnce(Return(false)); |
| |
| MountHelper mnt_helper(chronos_uid_, chronos_gid_, shared_gid_, kImageDir, |
| kSkelDir, helper_.system_salt, true /*legacy_mount*/, |
| &platform_); |
| |
| EXPECT_FALSE(mnt_helper.BindMyFilesDownloads(dest_dir)); |
| } |
| |
| TEST_P(MountTest, BindMyFilesDownloadsMissingMyFilesDownloads) { |
| FilePath dest_dir("/home/chronos/u-userhash"); |
| auto downloads_path = dest_dir.Append("Downloads"); |
| auto downloads_in_myfiles = dest_dir.Append("MyFiles").Append("Downloads"); |
| |
| // When MyFiles/Downloads doesn't exists BindMyFilesDownloads returns false. |
| EXPECT_CALL(platform_, DirectoryExists(dest_dir)).WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(downloads_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(downloads_in_myfiles)) |
| .WillOnce(Return(false)); |
| |
| MountHelper mnt_helper(chronos_uid_, chronos_gid_, shared_gid_, kImageDir, |
| kSkelDir, helper_.system_salt, true /*legacy_mount*/, |
| &platform_); |
| |
| EXPECT_FALSE(mnt_helper.BindMyFilesDownloads(dest_dir)); |
| } |
| |
| // A fixture for testing chaps directory checks. |
| class ChapsDirectoryTest : public ::testing::Test { |
| public: |
| ChapsDirectoryTest() |
| : kBaseDir("/base_chaps_dir"), |
| kSaltFile("/base_chaps_dir/auth_data_salt"), |
| kDatabaseDir("/base_chaps_dir/database"), |
| kDatabaseFile("/base_chaps_dir/database/file"), |
| kLegacyDir("/legacy"), |
| kRootUID(0), kRootGID(0), kChapsUID(1), kSharedGID(2), |
| mount_(new Mount()), |
| user_timestamp_cache_(new UserOldestActivityTimestampCache()) { |
| crypto_.set_platform(&platform_); |
| mount_->Init(&platform_, &crypto_, user_timestamp_cache_.get(), |
| base::DoNothing()); |
| mount_->chaps_user_ = kChapsUID; |
| mount_->default_access_group_ = kSharedGID; |
| // By default, set stats to the expected values. |
| InitStat(&base_stat_, 040750, kChapsUID, kSharedGID); |
| InitStat(&salt_stat_, 0600, kRootUID, kRootGID); |
| InitStat(&database_dir_stat_, 040750, kChapsUID, kSharedGID); |
| InitStat(&database_file_stat_, 0640, kChapsUID, kSharedGID); |
| } |
| |
| virtual ~ChapsDirectoryTest() {} |
| |
| void SetupFakeChapsDirectory() { |
| // Configure the base directory. |
| EXPECT_CALL(platform_, DirectoryExists(kBaseDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, Stat(kBaseDir, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(base_stat_), Return(true))); |
| |
| // Configure a fake enumerator. |
| MockFileEnumerator* enumerator = platform_.mock_enumerator(); |
| enumerator->entries_.push_back( |
| FileEnumerator::FileInfo(kBaseDir, base_stat_)); |
| enumerator->entries_.push_back( |
| FileEnumerator::FileInfo(kSaltFile, salt_stat_)); |
| enumerator->entries_.push_back( |
| FileEnumerator::FileInfo(kDatabaseDir, database_dir_stat_)); |
| enumerator->entries_.push_back( |
| FileEnumerator::FileInfo(kDatabaseFile, database_file_stat_)); |
| } |
| |
| bool RunCheck() { |
| return mount_->CheckChapsDirectory(kBaseDir, kLegacyDir); |
| } |
| |
| protected: |
| const FilePath kBaseDir; |
| const FilePath kSaltFile; |
| const FilePath kDatabaseDir; |
| const FilePath kDatabaseFile; |
| const FilePath kLegacyDir; |
| const uid_t kRootUID; |
| const gid_t kRootGID; |
| const uid_t kChapsUID; |
| const gid_t kSharedGID; |
| |
| struct stat base_stat_; |
| struct stat salt_stat_; |
| struct stat database_dir_stat_; |
| struct stat database_file_stat_; |
| |
| scoped_refptr<Mount> mount_; |
| NiceMock<MockPlatform> platform_; |
| NiceMock<MockCrypto> crypto_; |
| std::unique_ptr<UserOldestActivityTimestampCache> user_timestamp_cache_; |
| |
| private: |
| void InitStat(struct stat* s, mode_t mode, uid_t uid, gid_t gid) { |
| memset(s, 0, sizeof(struct stat)); |
| s->st_mode = mode; |
| s->st_uid = uid; |
| s->st_gid = gid; |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(ChapsDirectoryTest); |
| }; |
| |
| TEST_F(ChapsDirectoryTest, DirectoryOK) { |
| SetupFakeChapsDirectory(); |
| ASSERT_TRUE(RunCheck()); |
| } |
| |
| TEST_F(ChapsDirectoryTest, DirectoryDoesNotExist) { |
| // Specify directory does not exist. |
| EXPECT_CALL(platform_, DirectoryExists(kBaseDir)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, DirectoryExists(kLegacyDir)) |
| .WillRepeatedly(Return(false)); |
| // Expect basic setup. |
| EXPECT_CALL(platform_, CreateDirectory(kBaseDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetPermissions(kBaseDir, 0750)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetOwnership(kBaseDir, kChapsUID, kSharedGID, true)) |
| .WillRepeatedly(Return(true)); |
| ASSERT_TRUE(RunCheck()); |
| } |
| |
| TEST_F(ChapsDirectoryTest, CreateFailure) { |
| // Specify directory does not exist. |
| EXPECT_CALL(platform_, DirectoryExists(kBaseDir)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, DirectoryExists(kLegacyDir)) |
| .WillRepeatedly(Return(false)); |
| // Expect basic setup but fail. |
| EXPECT_CALL(platform_, CreateDirectory(kBaseDir)) |
| .WillRepeatedly(Return(false)); |
| ASSERT_FALSE(RunCheck()); |
| } |
| |
| TEST_F(ChapsDirectoryTest, FixBadPerms) { |
| // Specify some bad perms. |
| base_stat_.st_mode = 040700; |
| salt_stat_.st_mode = 0640; |
| database_dir_stat_.st_mode = 040755; |
| database_file_stat_.st_mode = 0666; |
| SetupFakeChapsDirectory(); |
| // Expect corrections. |
| EXPECT_CALL(platform_, SetPermissions(kBaseDir, 0750)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetPermissions(kSaltFile, 0600)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetPermissions(kDatabaseDir, 0750)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetPermissions(kDatabaseFile, 0640)) |
| .WillRepeatedly(Return(true)); |
| ASSERT_TRUE(RunCheck()); |
| } |
| |
| TEST_F(ChapsDirectoryTest, FixBadOwnership) { |
| // Specify bad ownership. |
| base_stat_.st_uid = kRootUID; |
| salt_stat_.st_gid = kChapsUID; |
| database_dir_stat_.st_gid = kChapsUID; |
| database_file_stat_.st_uid = kSharedGID; |
| SetupFakeChapsDirectory(); |
| // Expect corrections. |
| EXPECT_CALL(platform_, SetOwnership(kBaseDir, kChapsUID, kSharedGID, true)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetOwnership(kSaltFile, kRootUID, kRootGID, true)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| SetOwnership(kDatabaseDir, kChapsUID, kSharedGID, true)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| SetOwnership(kDatabaseFile, kChapsUID, kSharedGID, true)) |
| .WillRepeatedly(Return(true)); |
| ASSERT_TRUE(RunCheck()); |
| } |
| |
| TEST_F(ChapsDirectoryTest, FixBadPermsFailure) { |
| // Specify some bad perms. |
| base_stat_.st_mode = 040700; |
| SetupFakeChapsDirectory(); |
| // Expect corrections but fail to apply. |
| EXPECT_CALL(platform_, SetPermissions(_, _)) |
| .WillRepeatedly(Return(false)); |
| ASSERT_FALSE(RunCheck()); |
| } |
| |
| TEST_F(ChapsDirectoryTest, FixBadOwnershipFailure) { |
| // Specify bad ownership. |
| base_stat_.st_uid = kRootUID; |
| SetupFakeChapsDirectory(); |
| // Expect corrections but fail to apply. |
| EXPECT_CALL(platform_, SetOwnership(_, _, _, _)) |
| .WillRepeatedly(Return(false)); |
| ASSERT_FALSE(RunCheck()); |
| } |
| |
| TEST_P(MountTest, CheckChapsDirectoryMigration) { |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| |
| // Configure stub methods. |
| EXPECT_CALL(platform_, Copy(_, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, DeleteFile(_, _)) |
| .WillRepeatedly(Return(true)); |
| |
| // Stubs which will trigger the migration code path. |
| EXPECT_CALL(platform_, DirectoryExists(FilePath("/fake"))) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, DirectoryExists(FilePath("/fake_legacy"))) |
| .WillRepeatedly(Return(true)); |
| |
| // Configure stat for the base directory. |
| struct stat base_stat = {0}; |
| base_stat.st_mode = 040123; |
| base_stat.st_uid = 1; |
| base_stat.st_gid = 2; |
| EXPECT_CALL(platform_, Stat(_, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(base_stat), Return(true))); |
| |
| // Configure a fake enumerator. |
| MockFileEnumerator* enumerator = platform_.mock_enumerator(); |
| struct stat file_info1 = {0}; |
| file_info1.st_mode = 0555; |
| file_info1.st_uid = 3; |
| file_info1.st_gid = 4; |
| struct stat file_info2 = {0}; |
| file_info2.st_mode = 0777; |
| file_info2.st_uid = 5; |
| file_info2.st_gid = 6; |
| enumerator->entries_.push_back( |
| FileEnumerator::FileInfo( |
| FilePath("/fake_legacy/test_file1"), file_info1)); |
| enumerator->entries_.push_back( |
| FileEnumerator::FileInfo( |
| FilePath("test_file2"), file_info2)); |
| |
| // These expectations will ensure the ownership and permissions are being |
| // correctly applied after the directory has been moved. |
| EXPECT_CALL(platform_, SetOwnership(FilePath("/fake/test_file1"), 3, 4, true)) |
| .Times(1); |
| EXPECT_CALL(platform_, |
| SetPermissions(FilePath("/fake/test_file1"), 0555)).Times(1); |
| EXPECT_CALL(platform_, SetOwnership(FilePath("/fake/test_file2"), 5, 6, true)) |
| .Times(1); |
| EXPECT_CALL(platform_, |
| SetPermissions(FilePath("/fake/test_file2"), 0777)).Times(1); |
| EXPECT_CALL(platform_, SetOwnership(FilePath("/fake"), 1, 2, true)).Times(1); |
| EXPECT_CALL(platform_, |
| SetPermissions(FilePath("/fake"), 0123)).Times(1); |
| |
| DoMountInit(); |
| EXPECT_TRUE(mount_->CheckChapsDirectory( |
| FilePath("/fake"), FilePath("/fake_legacy"))); |
| } |
| |
| TEST_P(MountTest, CreateCryptohomeTest) { |
| InsertTestUsers(&kDefaultUsers[5], 1); |
| // Creates a cryptohome and tests credentials. |
| HomeDirs homedirs; |
| homedirs.set_shadow_root(kImageDir); |
| |
| TestUser *user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| EXPECT_TRUE(DoMountInit()); |
| EXPECT_TRUE(homedirs.Init(&platform_, mount_->crypto(), |
| user_timestamp_cache_.get())); |
| |
| // TODO(wad) Make this into a UserDoesntExist() helper. |
| EXPECT_CALL(platform_, FileExists(user->image_path)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, FileExists(user->keyset_path)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, |
| CreateDirectory( |
| AnyOf(user->mount_prefix, |
| user->user_mount_prefix, |
| user->user_mount_path, |
| user->root_mount_prefix, |
| user->root_mount_path))) |
| .Times(7) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| CreateDirectory( |
| AnyOf(FilePath("/home/chronos"), |
| MountHelper::GetNewUserPath(user->username)))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(user->vault_path)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, DirectoryExists(user->vault_mount_path)) |
| .WillRepeatedly(Return(false)); |
| if (ShouldTestEcryptfs()) { |
| EXPECT_CALL(platform_, CreateDirectory(user->vault_path)) |
| .WillOnce(Return(true)); |
| } |
| EXPECT_CALL(platform_, CreateDirectory(user->base_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| brillo::Blob creds; |
| EXPECT_CALL(platform_, |
| WriteFileAtomicDurable(user->keyset_path, _, _)) |
| .WillOnce(DoAll(SaveArg<1>(&creds), Return(true))); |
| |
| bool created; |
| ASSERT_TRUE( |
| mount_->EnsureCryptohome(credentials, GetDefaultMountArgs(), &created)); |
| ASSERT_TRUE(created); |
| ASSERT_NE(creds.size(), 0); |
| ASSERT_FALSE(mount_->AreValid(credentials)); |
| { |
| InSequence s; |
| MockFileEnumerator* files = new MockFileEnumerator(); |
| EXPECT_CALL(platform_, GetFileEnumerator(user->base_path, false, _)) |
| .WillOnce(Return(files)); |
| // Single key. |
| EXPECT_CALL(*files, Next()) |
| .WillOnce(Return(user->keyset_path)); |
| EXPECT_CALL(*files, Next()) |
| .WillRepeatedly(Return(FilePath())); |
| } |
| |
| EXPECT_CALL(platform_, ReadFile(user->keyset_path, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(creds), Return(true))); |
| |
| ASSERT_TRUE(homedirs.AreCredentialsValid(credentials)); |
| } |
| |
| TEST_P(MountTest, GoodReDecryptTest) { |
| InsertTestUsers(&kDefaultUsers[6], 1); |
| // Create a Mount instance that points to a good shadow root, test that it |
| // properly re-authenticates against the first key. |
| mount_->set_use_tpm(true); |
| crypto_.set_use_tpm(true); |
| |
| TestUser *user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| EXPECT_CALL(tpm_init_, HasCryptohomeKey()) |
| .WillOnce(Return(false)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_init_, SetupTpm(true)) |
| .WillOnce(Return(true)) // This is by crypto.Init() and |
| .WillOnce(Return(true)); // cause we forced HasCryptohomeKey to false once. |
| crypto_.Init(&tpm_init_); |
| |
| EXPECT_CALL(tpm_, IsEnabled()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_TRUE(DoMountInit()); |
| |
| // Load the pre-generated keyset |
| FilePath key_path = mount_->GetUserLegacyKeyFileForUser( |
| credentials.GetObfuscatedUsername(helper_.system_salt), 0); |
| EXPECT_GT(key_path.value().size(), 0u); |
| cryptohome::SerializedVaultKeyset serialized; |
| EXPECT_TRUE(serialized.ParseFromArray(user->credentials.data(), |
| user->credentials.size())); |
| // Ensure we're starting from scrypt so we can test migrate to a mock-TPM. |
| EXPECT_EQ((serialized.flags() & SerializedVaultKeyset::SCRYPT_WRAPPED), |
| SerializedVaultKeyset::SCRYPT_WRAPPED); |
| EXPECT_EQ((serialized.flags() & SerializedVaultKeyset::TPM_WRAPPED), 0); |
| |
| // Call DecryptVaultKeyset first, allowing migration (the test data is not |
| // scrypt nor TPM wrapped) to a TPM-wrapped keyset |
| VaultKeyset vault_keyset; |
| vault_keyset.Initialize(&platform_, mount_->crypto()); |
| MountError error = MOUNT_ERROR_NONE; |
| // Inject the pre-generated, scrypt-wrapped keyset. |
| EXPECT_CALL(platform_, FileExists(user->keyset_path)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, ReadFile(user->keyset_path, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(user->credentials), |
| Return(true))); |
| EXPECT_CALL(platform_, FileExists(user->salt_path)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, ReadFile(user->salt_path, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(user->user_salt), |
| Return(true))); |
| |
| EXPECT_CALL(platform_, |
| Move(user->keyset_path, user->keyset_path.AddExtension("bak"))) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| Move(user->salt_path, user->salt_path.AddExtension("bak"))) |
| .WillOnce(Return(true)); |
| |
| // Create the "TPM-wrapped" value by letting it save the plaintext. |
| EXPECT_CALL(tpm_, SealToPcrWithAuthorization(_, _, _, _, _)) |
| .WillRepeatedly(Invoke(TpmPassthroughSealWithAuthorization)); |
| brillo::SecureBlob fake_pub_key("A"); |
| EXPECT_CALL(tpm_, GetPublicKeyHash(_, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(fake_pub_key), |
| Return(Tpm::kTpmRetryNone))); |
| |
| brillo::Blob migrated_keyset; |
| EXPECT_CALL(platform_, |
| WriteFileAtomicDurable(user->keyset_path, _, _)) |
| .WillOnce(DoAll(SaveArg<1>(&migrated_keyset), Return(true))); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| int key_index = 0; |
| |
| user->InjectKeyset(&platform_, true); |
| |
| EXPECT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &serialized, &key_index, &error)); |
| ASSERT_EQ(error, MOUNT_ERROR_NONE); |
| ASSERT_NE(migrated_keyset.size(), 0); |
| |
| cryptohome::SerializedVaultKeyset serialized_tpm; |
| EXPECT_TRUE(serialized_tpm.ParseFromArray(migrated_keyset.data(), |
| migrated_keyset.size())); |
| // Did it migrate? |
| EXPECT_EQ(SerializedVaultKeyset::PCR_BOUND, |
| (serialized_tpm.flags() & SerializedVaultKeyset::PCR_BOUND)); |
| EXPECT_EQ(SerializedVaultKeyset::TPM_WRAPPED, |
| (serialized_tpm.flags() & SerializedVaultKeyset::TPM_WRAPPED)); |
| EXPECT_EQ(0, (serialized.flags() & SerializedVaultKeyset::SCRYPT_WRAPPED)); |
| // Does it use scrypt for key derivation? |
| EXPECT_EQ(SerializedVaultKeyset::SCRYPT_DERIVED, |
| (serialized_tpm.flags() & SerializedVaultKeyset::SCRYPT_DERIVED)); |
| |
| // Inject the migrated keyset |
| Mock::VerifyAndClearExpectations(&platform_); |
| EXPECT_CALL(platform_, FileExists(user->keyset_path)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, ReadFile(user->keyset_path, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(migrated_keyset), |
| Return(true))); |
| EXPECT_CALL(platform_, FileExists(user->salt_path)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, ReadFile(user->salt_path, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(user->user_salt), |
| Return(true))); |
| EXPECT_CALL(tpm_, UnsealWithAuthorization(_, _, _, _, _)) |
| .WillRepeatedly(Invoke(TpmPassthroughDecrypt)); |
| |
| MockFileEnumerator* files = new MockFileEnumerator(); |
| EXPECT_CALL(platform_, GetFileEnumerator(user->base_path, false, _)) |
| .WillOnce(Return(files)); |
| // Single key. |
| { |
| InSequence s; |
| EXPECT_CALL(*files, Next()) |
| .WillOnce(Return(user->keyset_path)); |
| EXPECT_CALL(*files, Next()) |
| .WillOnce(Return(FilePath())); |
| } |
| |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| ASSERT_TRUE(homedirs_.AreCredentialsValid(credentials)); |
| } |
| |
| TEST_P(MountTest, TpmWrappedToPcrBoundMigrationTest) { |
| InsertTestUsers(&kDefaultUsers[6], 1); |
| // Create a Mount instance that points to a good shadow root, test that it |
| // properly re-authenticates against the first key. |
| mount_->set_use_tpm(true); |
| crypto_.set_use_tpm(true); |
| |
| TestUser *user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| EXPECT_CALL(tpm_init_, HasCryptohomeKey()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_init_, SetupTpm(true)) |
| .WillOnce(Return(true)); |
| crypto_.Init(&tpm_init_); |
| |
| EXPECT_CALL(tpm_, IsEnabled()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_TRUE(DoMountInit()); |
| |
| // Load the pre-generated keyset |
| FilePath key_path = mount_->GetUserLegacyKeyFileForUser( |
| credentials.GetObfuscatedUsername(helper_.system_salt), 0); |
| EXPECT_GT(key_path.value().size(), 0u); |
| cryptohome::SerializedVaultKeyset serialized; |
| EXPECT_TRUE(serialized.ParseFromArray(user->credentials.data(), |
| user->credentials.size())); |
| serialized.set_flags(SerializedVaultKeyset::TPM_WRAPPED | |
| SerializedVaultKeyset::SCRYPT_DERIVED); |
| |
| // Call DecryptVaultKeyset first, allowing migration to a PCR-bound keyset. |
| VaultKeyset vault_keyset; |
| vault_keyset.Initialize(&platform_, mount_->crypto()); |
| MountError error = MOUNT_ERROR_NONE; |
| // Inject the pre-generated, TPM-wrapped keyset. |
| EXPECT_CALL(platform_, FileExists(user->keyset_path)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, ReadFile(user->keyset_path, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(user->credentials), |
| Return(true))); |
| EXPECT_CALL(platform_, FileExists(user->salt_path)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, ReadFile(user->salt_path, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(user->user_salt), |
| Return(true))); |
| |
| EXPECT_CALL(platform_, |
| Move(user->keyset_path, user->keyset_path.AddExtension("bak"))) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| Move(user->salt_path, user->salt_path.AddExtension("bak"))) |
| .WillOnce(Return(true)); |
| |
| // |
| // Create the "TPM-wrapped" value by letting it save the plaintext. |
| EXPECT_CALL(tpm_, SealToPcrWithAuthorization(_, _, _, _, _)) |
| .WillRepeatedly(Invoke(TpmPassthroughSealWithAuthorization)); |
| brillo::SecureBlob fake_pub_key("A"); |
| EXPECT_CALL(tpm_, GetPublicKeyHash(_, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(fake_pub_key), |
| Return(Tpm::kTpmRetryNone))); |
| |
| brillo::Blob migrated_keyset; |
| EXPECT_CALL(platform_, |
| WriteFileAtomicDurable(user->keyset_path, _, _)) |
| .WillOnce(DoAll(SaveArg<1>(&migrated_keyset), Return(true))); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| int key_index = 0; |
| |
| user->InjectKeyset(&platform_, true); |
| |
| EXPECT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &serialized, &key_index, &error)); |
| ASSERT_EQ(error, MOUNT_ERROR_NONE); |
| ASSERT_NE(migrated_keyset.size(), 0); |
| |
| cryptohome::SerializedVaultKeyset serialized_tpm; |
| EXPECT_TRUE(serialized_tpm.ParseFromArray(migrated_keyset.data(), |
| migrated_keyset.size())); |
| // Did it migrate? |
| EXPECT_EQ(SerializedVaultKeyset::PCR_BOUND, |
| (serialized_tpm.flags() & SerializedVaultKeyset::PCR_BOUND)); |
| EXPECT_EQ(SerializedVaultKeyset::TPM_WRAPPED, |
| (serialized_tpm.flags() & SerializedVaultKeyset::TPM_WRAPPED)); |
| EXPECT_EQ(0, (serialized.flags() & SerializedVaultKeyset::SCRYPT_WRAPPED)); |
| // Does it use scrypt for key derivation? |
| EXPECT_EQ(SerializedVaultKeyset::SCRYPT_DERIVED, |
| (serialized_tpm.flags() & SerializedVaultKeyset::SCRYPT_DERIVED)); |
| } |
| |
| TEST_P(MountTest, MountCryptohome) { |
| // checks that cryptohome tries to mount successfully, and tests that the |
| // tracked directories are created/replaced as expected |
| InsertTestUsers(&kDefaultUsers[10], 1); |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| TestUser *user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| user->InjectUserPaths(&platform_, chronos_uid_, chronos_gid_, shared_gid_, |
| kDaemonGid, ShouldTestEcryptfs()); |
| user->InjectKeyset(&platform_, true); |
| |
| ExpectCryptohomeMount(*user); |
| EXPECT_CALL(platform_, ClearUserKeyring()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| |
| // user exists, so there'll be no skel copy after. |
| |
| MountError error = MOUNT_ERROR_NONE; |
| EXPECT_TRUE( |
| mount_->MountCryptohome(credentials, GetDefaultMountArgs(), &error)); |
| } |
| |
| TEST_P(MountTest, MountCryptohomeChapsKey) { |
| // Test to check if Cryptohome mount saves the chaps key correctly, |
| // and doesn't regenerate it. |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| InsertTestUsers(&kDefaultUsers[0], 1); |
| TestUser* user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| user->InjectKeyset(&platform_, true); |
| VaultKeyset vault_keyset; |
| vault_keyset.Initialize(&platform_, mount_->crypto()); |
| SerializedVaultKeyset serialized; |
| MountError error; |
| int key_index = -1; |
| |
| // First we decrypt the vault to load the chaps key. |
| ASSERT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &serialized, &key_index, &error)); |
| EXPECT_EQ(key_index, 0); |
| EXPECT_EQ(serialized.has_wrapped_chaps_key(), true); |
| |
| SecureBlob local_chaps(vault_keyset.chaps_key().begin(), |
| vault_keyset.chaps_key().end()); |
| user->InjectUserPaths(&platform_, chronos_uid_, chronos_gid_, |
| shared_gid_, kDaemonGid, ShouldTestEcryptfs()); |
| user->InjectKeyset(&platform_, true); |
| |
| ExpectCryptohomeMount(*user); |
| |
| ASSERT_TRUE( |
| mount_->MountCryptohome(credentials, GetDefaultMountArgs(), &error)); |
| |
| user->InjectKeyset(&platform_, true); |
| |
| ASSERT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &serialized, &key_index, &error)); |
| |
| // Compare the pre mount chaps key to the post mount key. |
| ASSERT_EQ(local_chaps.size(), vault_keyset.chaps_key().size()); |
| ASSERT_EQ(0, brillo::SecureMemcmp(local_chaps.data(), |
| vault_keyset.chaps_key().data(), local_chaps.size())); |
| } |
| |
| TEST_P(MountTest, MountCryptohomeNoChapsKey) { |
| // This test checks if the mount operation recreates the chaps key |
| // if it isn't present in the vault. |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| InsertTestUsers(&kDefaultUsers[0], 1); |
| TestUser* user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| user->InjectKeyset(&platform_, true); |
| VaultKeyset vault_keyset; |
| vault_keyset.Initialize(&platform_, mount_->crypto()); |
| SerializedVaultKeyset serialized; |
| MountError error; |
| int key_index = -1; |
| EXPECT_CALL(platform_, ReadFile(user->keyset_path, _)) |
| .Times(2) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(user->credentials), |
| Return(true))); |
| |
| ASSERT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &serialized, &key_index, &error)); |
| |
| vault_keyset.clear_chaps_key(); |
| EXPECT_CALL(platform_, FileExists(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, DeleteFile(_, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, Move(_, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| WriteFileAtomicDurable(user->keyset_path, _, _)) |
| .WillRepeatedly(DoAll(SaveArg<1>(&(user->credentials)), Return(true))); |
| ASSERT_TRUE(mount_->ReEncryptVaultKeyset(credentials, key_index, |
| &vault_keyset, &serialized)); |
| EXPECT_CALL(platform_, ReadFile(user->keyset_path, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(user->credentials), |
| Return(true))); |
| user->InjectKeyset(&platform_, true); |
| ASSERT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &serialized, &key_index, &error)); |
| |
| EXPECT_EQ(key_index, 0); |
| EXPECT_EQ(serialized.has_wrapped_chaps_key(), false); |
| |
| user->InjectUserPaths(&platform_, chronos_uid_, chronos_gid_, |
| shared_gid_, kDaemonGid, ShouldTestEcryptfs()); |
| user->InjectKeyset(&platform_, true); |
| |
| ExpectCryptohomeMount(*user); |
| |
| ASSERT_TRUE( |
| mount_->MountCryptohome(credentials, GetDefaultMountArgs(), &error)); |
| EXPECT_CALL(platform_, ReadFile(user->keyset_path, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(user->credentials), |
| Return(true))); |
| user->InjectKeyset(&platform_, true); |
| ASSERT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &serialized, &key_index, &error)); |
| EXPECT_EQ(serialized.has_wrapped_chaps_key(), true); |
| EXPECT_EQ(vault_keyset.chaps_key().size(), CRYPTOHOME_CHAPS_KEY_LENGTH); |
| } |
| |
| TEST_P(MountTest, MountCryptohomeLECredentials) { |
| // This test checks the mount operation with the LE credentials. |
| InitializeLECredential(); |
| EXPECT_CALL(*le_cred_manager_, NeedsPcrBinding(_)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| |
| VaultKeyset pin_vault_keyset; |
| pin_vault_keyset.Initialize(&platform_, mount_->crypto()); |
| |
| SerializedVaultKeyset serialized; |
| MountError error; |
| int key_index = -1; |
| ASSERT_TRUE(mount_->DecryptVaultKeyset(*pin_credentials_, &pin_vault_keyset, |
| &serialized, &key_index, &error)); |
| } |
| |
| TEST_P(MountTest, MountCryptohomeLECredentialsMigrate) { |
| // This test checks if the mount operation recreates the LE credentials |
| // when the switch to protocol 1 happens. |
| InitializeLECredential(); |
| EXPECT_CALL(*le_cred_manager_, NeedsPcrBinding(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| |
| brillo::Blob creds; |
| EXPECT_CALL(platform_, FileExists(_)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, WriteFileAtomicDurable(pin_user_->keyset_path, _, _)) |
| .WillOnce(DoAll(SaveArg<1>(&creds), Return(true))); |
| |
| // Make sure the same reset_secret is inserted. |
| EXPECT_CALL( |
| *le_cred_manager_, |
| InsertCredential(_, _, SecureBlob(HexDecode(kHexResetSecret)), _, _, _)) |
| .WillOnce(Return(LE_CRED_SUCCESS)); |
| EXPECT_CALL(*le_cred_manager_, RemoveCredential(_)) |
| .WillOnce(Return(LE_CRED_SUCCESS)); |
| |
| VaultKeyset pin_vault_keyset; |
| pin_vault_keyset.Initialize(&platform_, mount_->crypto()); |
| |
| SerializedVaultKeyset serialized; |
| MountError error; |
| int key_index = -1; |
| ASSERT_TRUE(mount_->DecryptVaultKeyset(*pin_credentials_, &pin_vault_keyset, |
| &serialized, &key_index, &error)); |
| |
| // Check the inserted data from migration. |
| ASSERT_TRUE(LoadSerializedKeyset(creds, &serialized)); |
| ASSERT_TRUE(serialized.flags() & SerializedVaultKeyset::LE_CREDENTIAL); |
| } |
| |
| TEST_P(MountTest, MountCryptohomeLECredentialsMigrationFails) { |
| // This test checks the scenario when mount operation tries to recreate |
| // vault keyset for LE credentials and insert operation fails. |
| InitializeLECredential(); |
| EXPECT_CALL(platform_, FileExists(_)).WillRepeatedly(Return(false)); |
| EXPECT_CALL(*le_cred_manager_, NeedsPcrBinding(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| |
| EXPECT_CALL(platform_, FileExists(_)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, |
| WriteSecureBlobToFileAtomicDurable(_, _, _)).Times(0); |
| |
| EXPECT_CALL(*le_cred_manager_, InsertCredential( |
| _, _, SecureBlob(HexDecode(kHexResetSecret)), _, _, _)) |
| .WillOnce(Return(LE_CRED_ERROR_NO_FREE_LABEL)); |
| EXPECT_CALL(*le_cred_manager_, RemoveCredential(_)).Times(0); |
| |
| VaultKeyset pin_vault_keyset; |
| pin_vault_keyset.Initialize(&platform_, mount_->crypto()); |
| |
| SerializedVaultKeyset serialized; |
| MountError error; |
| int key_index = -1; |
| ASSERT_TRUE(mount_->DecryptVaultKeyset(*pin_credentials_, &pin_vault_keyset, |
| &serialized, &key_index, &error)); |
| |
| // Check the returned data. |
| ASSERT_TRUE(serialized.flags() & SerializedVaultKeyset::LE_CREDENTIAL); |
| } |
| |
| TEST_P(MountTest, MountCryptohomeNoChange) { |
| // Checks that cryptohome doesn't by default re-save the cryptohome on mount. |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| InsertTestUsers(&kDefaultUsers[11], 1); |
| TestUser* user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| user->InjectKeyset(&platform_, true); |
| VaultKeyset vault_keyset; |
| vault_keyset.Initialize(&platform_, mount_->crypto()); |
| SerializedVaultKeyset serialized; |
| MountError error; |
| int key_index = -1; |
| |
| ASSERT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &serialized, &key_index, &error)); |
| EXPECT_EQ(key_index, 0); |
| |
| user->InjectUserPaths(&platform_, chronos_uid_, chronos_gid_, |
| shared_gid_, kDaemonGid, ShouldTestEcryptfs()); |
| user->InjectKeyset(&platform_, true); |
| |
| ExpectCryptohomeMount(*user); |
| |
| ASSERT_TRUE( |
| mount_->MountCryptohome(credentials, GetDefaultMountArgs(), &error)); |
| |
| user->InjectKeyset(&platform_, true); |
| |
| SerializedVaultKeyset new_serialized; |
| ASSERT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &new_serialized, &key_index, &error)); |
| |
| SecureBlob lhs; |
| GetKeysetBlob(serialized, &lhs); |
| SecureBlob rhs; |
| GetKeysetBlob(new_serialized, &rhs); |
| ASSERT_EQ(lhs.size(), rhs.size()); |
| ASSERT_EQ(0, brillo::SecureMemcmp(lhs.data(), rhs.data(), lhs.size())); |
| } |
| |
| TEST_P(MountTest, MountCryptohomeNoCreate) { |
| // Checks that doesn't create the cryptohome for the user on Mount without |
| // being told to do so. |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| // Test user at index 12 hasn't been created. |
| InsertTestUsers(&kDefaultUsers[12], 1); |
| TestUser* user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| // Doesn't exist. |
| EXPECT_CALL(platform_, DirectoryExists(user->vault_path)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, DirectoryExists(user->vault_mount_path)) |
| .WillOnce(Return(false)); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = false; |
| MountError error = MOUNT_ERROR_NONE; |
| ASSERT_FALSE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| ASSERT_EQ(MOUNT_ERROR_USER_DOES_NOT_EXIST, error); |
| |
| // Now let it create the vault. |
| // TODO(wad) Drop NiceMock and replace with InSequence EXPECT_CALL()s. |
| // It will complain about creating tracked subdirs, but that is non-fatal. |
| Mock::VerifyAndClearExpectations(&platform_); |
| user->InjectKeyset(&platform_, true); |
| |
| EXPECT_CALL(platform_, |
| DirectoryExists(AnyOf(user->vault_path, user->vault_mount_path, |
| user->user_vault_path))) |
| .Times(4) |
| .WillRepeatedly(Return(false)); |
| |
| // Not legacy |
| EXPECT_CALL(platform_, FileExists(user->image_path)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| |
| EXPECT_CALL(platform_, GetFileEnumerator(kSkelDir, _, _)) |
| .WillOnce(Return(new NiceMock<MockFileEnumerator>())) |
| .WillOnce(Return(new NiceMock<MockFileEnumerator>())); |
| |
| EXPECT_CALL(platform_, CreateDirectory(_)) |
| .WillRepeatedly(Return(true)); |
| brillo::Blob creds; |
| EXPECT_CALL(platform_, |
| WriteFileAtomicDurable(user->keyset_path, _, _)) |
| .WillOnce(DoAll(SaveArg<1>(&creds), Return(true))) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(platform_, SetOwnership(_, _, _, _)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetPermissions(_, _)).WillRepeatedly(Return(true)); |
| |
| ExpectCryptohomeMount(*user); |
| |
| // Fake successful mount to /home/chronos/user/* |
| EXPECT_CALL(platform_, |
| FileExists( |
| Property(&FilePath::value, AnyOf( |
| StartsWith(user->legacy_user_mount_path.value()), |
| StartsWith(user->vault_mount_path.value()))))) |
| .WillRepeatedly(Return(true)); |
| |
| mount_args.create_if_missing = true; |
| error = MOUNT_ERROR_NONE; |
| ASSERT_TRUE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| ASSERT_EQ(MOUNT_ERROR_NONE, error); |
| } |
| |
| TEST_P(MountTest, UserActivityTimestampUpdated) { |
| // checks that user activity timestamp is updated during Mount() and |
| // periodically while mounted, other Keyset fields remains the same |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| InsertTestUsers(&kDefaultUsers[9], 1); |
| TestUser* user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| EXPECT_CALL(platform_, |
| CreateDirectory( |
| AnyOf( |
| MountHelper::GetNewUserPath(user->username), |
| Property(&FilePath::value, StartsWith(kImageDir.value()))))) |
| .WillRepeatedly(Return(true)); |
| |
| user->InjectKeyset(&platform_, true); |
| user->InjectUserPaths(&platform_, chronos_uid_, chronos_gid_, |
| shared_gid_, kDaemonGid, ShouldTestEcryptfs()); |
| |
| // Mount() |
| MountError error; |
| ExpectCryptohomeMount(*user); |
| ASSERT_TRUE( |
| mount_->MountCryptohome(credentials, GetDefaultMountArgs(), &error)); |
| |
| // Update the timestamp. Normally it is called in MountTask::Run() in |
| // background but here in the test we must call it manually. |
| static const int kMagicTimestamp = 123; |
| brillo::Blob updated_keyset; |
| std::string timestamp_str; |
| EXPECT_CALL(platform_, |
| WriteStringToFileAtomicDurable(user->timestamp_path, |
| _, _)) |
| .WillRepeatedly(DoAll(SaveArg<1>(×tamp_str), Return(true))); |
| EXPECT_CALL(platform_, GetCurrentTime()) |
| .WillOnce(Return(base::Time::FromInternalValue(kMagicTimestamp))); |
| mount_->UpdateCurrentUserActivityTimestamp(0); |
| |
| // Check that last activity timestamp is updated. |
| ASSERT_TRUE(platform_.FileExists(user->timestamp_path)); |
| Timestamp tstamp; |
| tstamp.ParseFromString(timestamp_str); |
| EXPECT_EQ(kMagicTimestamp, tstamp.timestamp()); |
| |
| // Unmount the user. This must update user's activity timestamps. |
| static const int kMagicTimestamp2 = 234; |
| EXPECT_CALL(platform_, GetCurrentTime()) |
| .WillOnce(Return(base::Time::FromInternalValue(kMagicTimestamp2))); |
| EXPECT_CALL(platform_, Unmount(_, _, _)).WillRepeatedly(Return(true)); |
| mount_->UnmountCryptohome(); |
| Timestamp tstamp2; |
| tstamp2.ParseFromString(timestamp_str); |
| EXPECT_EQ(kMagicTimestamp2, tstamp2.timestamp()); |
| |
| |
| // Update timestamp again, after user is unmounted. User's activity |
| // timestamp must not change this. |
| mount_->UpdateCurrentUserActivityTimestamp(0); |
| Timestamp tstamp3; |
| tstamp3.ParseFromString(timestamp_str); |
| EXPECT_EQ(tstamp3.timestamp(), |
| tstamp2.timestamp()); |
| } |
| |
| TEST_P(MountTest, RememberMountOrderingTest) { |
| // Checks that mounts made with MountAndPush/BindAndPush are undone in the |
| // right order. |
| MountHelper mnt_helper(chronos_uid_, chronos_gid_, shared_gid_, kImageDir, |
| kSkelDir, helper_.system_salt, true /*legacy_mount*/, |
| &platform_); |
| |
| FilePath src("/src"); |
| FilePath dest0("/dest/foo"); |
| FilePath dest1("/dest/bar"); |
| FilePath dest2("/dest/baz"); |
| { |
| InSequence sequence; |
| EXPECT_CALL(platform_, Mount(src, dest0, _, kDefaultMountFlags, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, Bind(src, dest1)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, Mount(src, dest2, _, kDefaultMountFlags, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, Unmount(dest2, _, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, Unmount(dest1, _, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, Unmount(dest0, _, _)) |
| .WillOnce(Return(true)); |
| |
| EXPECT_TRUE(mnt_helper.MountAndPush(src, dest0, "", "")); |
| EXPECT_TRUE(mnt_helper.BindAndPush(src, dest1)); |
| EXPECT_TRUE(mnt_helper.MountAndPush(src, dest2, "", "")); |
| mnt_helper.UnmountAll(); |
| } |
| } |
| |
| TEST_P(MountTest, LockboxGetsFinalized) { |
| StrictMock<MockBootLockbox> lockbox; |
| mount_->set_boot_lockbox(&lockbox); |
| ASSERT_TRUE(DoMountInit()); |
| EXPECT_CALL(lockbox, FinalizeBoot()).Times(2).WillRepeatedly(Return(true)); |
| Credentials credentials("username", SecureBlob("password")); |
| Mount::MountArgs args = GetDefaultMountArgs(); |
| MountError error = MOUNT_ERROR_NONE; |
| EXPECT_EQ(premount_callback_counter_, 0); |
| mount_->MountCryptohome(credentials, args, &error); |
| mount_->MountGuestCryptohome(); |
| EXPECT_EQ(premount_callback_counter_, 2); |
| } |
| |
| TEST_P(MountTest, TwoWayKeysetMigrationTest) { |
| // Checks that in the following scenario the keyset is not corrupted |
| // 1) Have TPM present - keys are TPM wrapped. |
| // 2) Decrypt while no TPM - keys are migrated to Scrypt. |
| // 3) Decrypt with TPM again - keys are migrated back to TPM. |
| |
| // Start with TPM enabled |
| mount_->set_use_tpm(true); |
| crypto_.set_use_tpm(true); |
| |
| // TPM-wrapped is just plaintext |
| brillo::SecureBlob fake_pub_key("A"); |
| EXPECT_CALL(tpm_, GetPublicKeyHash(_, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(fake_pub_key), |
| Return(Tpm::kTpmRetryNone))); |
| EXPECT_CALL(tpm_, DecryptBlob(_, _, _, _, _)) |
| .WillRepeatedly(Invoke(TpmPassthroughDecrypt)); |
| |
| // TPM calls are always ok. Control TPM presence with set_use_tpm() |
| EXPECT_CALL(tpm_init_, HasCryptohomeKey()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_init_, SetupTpm(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsEnabled()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()) |
| .WillRepeatedly(Return(true)); |
| crypto_.Init(&tpm_init_); |
| |
| InsertTestUsers(&kDefaultUsers[7], 1); |
| TestUser *user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| user->InjectKeyset(&platform_, true); |
| // We now have Scrypt-wrapped key injected |
| |
| // Mock file and homedir ops |
| HomeDirs homedirs; |
| homedirs.set_shadow_root(kImageDir); |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| int key_index = 0; |
| |
| // Allow the "backup"s to be written during migrations |
| EXPECT_CALL(platform_, FileExists(user->keyset_path.AddExtension("bak"))) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, FileExists(user->salt_path.AddExtension("bak"))) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, |
| Move(user->keyset_path, user->keyset_path.AddExtension("bak"))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| Move(user->salt_path, user->salt_path.AddExtension("bak"))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| |
| // Capture the migrated keysets when written to file |
| brillo::Blob migrated_keyset; |
| EXPECT_CALL(platform_, WriteFileAtomicDurable(user->keyset_path, _, _)) |
| .WillRepeatedly(DoAll(SaveArg<1>(&migrated_keyset), Return(true))); |
| |
| EXPECT_CALL(platform_, FileExists(user->salt_path)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, ReadFile(user->salt_path, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(user->user_salt), |
| Return(true))); |
| |
| // Step 1: TPM is present. Get a TPM-wrapped key. |
| VaultKeyset vault_keyset; |
| vault_keyset.Initialize(&platform_, mount_->crypto()); |
| |
| MountError error; |
| cryptohome::SerializedVaultKeyset serialized; |
| |
| // Migrate to TPM-wrapped from the original Scrypt-wrapped |
| error = MOUNT_ERROR_NONE; |
| EXPECT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &serialized, &key_index, &error)); |
| ASSERT_EQ(error, MOUNT_ERROR_NONE); |
| ASSERT_NE(migrated_keyset.size(), 0); |
| |
| // Check and fix the flags if needed |
| // Erroneous cryptohome code might have set the TPM vs Scrypt flags |
| // incorrectly. We (a) check for it here, (b) reset flags to the |
| // correct value to complete the rest of the test that needs |
| // TPM-wrapped keys with correct flags |
| error = MOUNT_ERROR_NONE; |
| user->InjectKeyset(&platform_, true); |
| |
| EXPECT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &serialized, &key_index, &error)); |
| |
| unsigned int flags = serialized.flags(); |
| EXPECT_EQ((flags & SerializedVaultKeyset::TPM_WRAPPED), |
| SerializedVaultKeyset::TPM_WRAPPED); |
| EXPECT_EQ((flags & SerializedVaultKeyset::SCRYPT_WRAPPED), 0); |
| |
| if (flags & SerializedVaultKeyset::SCRYPT_WRAPPED) { |
| EXPECT_CALL(platform_, ReadFile(user->keyset_path, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(migrated_keyset), |
| Return(true))); |
| serialized.set_flags(flags & ~SerializedVaultKeyset::SCRYPT_WRAPPED); |
| EXPECT_TRUE(mount_->ReEncryptVaultKeyset(credentials, 0, &vault_keyset, |
| &serialized)); |
| } |
| // Now we have the TPM-wrapped keyset with correct flags |
| |
| // Step 2: No TPM. Migrate to Scrypt-wrapped. |
| mount_->set_use_tpm(false); |
| crypto_.set_use_tpm(false); |
| |
| error = MOUNT_ERROR_NONE; |
| user->InjectKeyset(&platform_, true); |
| |
| EXPECT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &serialized, &key_index, &error)); |
| ASSERT_EQ(error, MOUNT_ERROR_NONE); |
| ASSERT_NE(migrated_keyset.size(), 0); |
| |
| // Step 3: TPM back on. Migrate to TPM-wrapped. |
| // If flags were set incorrectly by the previous migration (i.e it is |
| // Scrypt-wrapped w/ both TPM and Scrypt flags set), Decrypt will fail. |
| mount_->set_use_tpm(true); |
| crypto_.set_use_tpm(true); |
| |
| error = MOUNT_ERROR_NONE; |
| user->InjectKeyset(&platform_, true); |
| |
| ASSERT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &serialized, &key_index, &error)); |
| ASSERT_EQ(error, MOUNT_ERROR_NONE); |
| } |
| |
| TEST_P(MountTest, BothFlagsMigrationTest) { |
| // Checks that in the following scenario works: |
| // TPM is enabled. |
| // We have a keyset that has both TPM and Scrypt flags set. |
| // When we decrypt it, mount re-encrypts and keeps only TPM |
| // flag set |
| |
| mount_->set_use_tpm(true); |
| crypto_.set_use_tpm(true); |
| |
| // TPM-wrapped is just plaintext |
| brillo::SecureBlob fake_pub_key("A"); |
| EXPECT_CALL(tpm_, GetPublicKeyHash(_, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(fake_pub_key), |
| Return(Tpm::kTpmRetryNone))); |
| EXPECT_CALL(tpm_, DecryptBlob(_, _, _, _, _)) |
| .WillRepeatedly(Invoke(TpmPassthroughDecrypt)); |
| |
| // TPM calls are always ok. Control TPM presence with set_use_tpm() |
| EXPECT_CALL(tpm_init_, HasCryptohomeKey()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_init_, SetupTpm(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsEnabled()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()) |
| .WillRepeatedly(Return(true)); |
| crypto_.Init(&tpm_init_); |
| |
| InsertTestUsers(&kDefaultUsers[7], 1); |
| TestUser *user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| user->InjectKeyset(&platform_, true); |
| // We now have Scrypt-wrapped key injected |
| |
| // Mock file and homedir ops |
| HomeDirs homedirs; |
| homedirs.set_shadow_root(kImageDir); |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| int key_index = 0; |
| |
| // Allow the "backup"s to be written during migrations |
| EXPECT_CALL(platform_, |
| FileExists(user->keyset_path.AddExtension("bak"))) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, |
| FileExists(user->salt_path.AddExtension("bak"))) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, |
| Move(user->keyset_path, |
| user->keyset_path.AddExtension("bak"))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| Move(user->salt_path, user->salt_path.AddExtension("bak"))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| |
| // Capture the migrated keysets when written to file |
| brillo::Blob migrated_keyset; |
| EXPECT_CALL(platform_, WriteFileAtomicDurable(user->keyset_path, _, _)) |
| .WillRepeatedly(DoAll(SaveArg<1>(&migrated_keyset), Return(true))); |
| |
| EXPECT_CALL(platform_, FileExists(user->salt_path)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, ReadFile(user->salt_path, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(user->user_salt), |
| Return(true))); |
| |
| // First, get a TPM-wrapped key from the original Scrypt-wrapped |
| VaultKeyset vault_keyset; |
| vault_keyset.Initialize(&platform_, mount_->crypto()); |
| |
| MountError error; |
| cryptohome::SerializedVaultKeyset serialized; |
| |
| error = MOUNT_ERROR_NONE; |
| EXPECT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &serialized, &key_index, &error)); |
| ASSERT_EQ(error, MOUNT_ERROR_NONE); |
| ASSERT_NE(migrated_keyset.size(), 0); |
| |
| // Now set both flags and write it |
| unsigned int flags = serialized.flags(); |
| EXPECT_EQ((flags & SerializedVaultKeyset::TPM_WRAPPED), |
| SerializedVaultKeyset::TPM_WRAPPED); |
| EXPECT_EQ((flags & SerializedVaultKeyset::SCRYPT_WRAPPED), 0); |
| |
| serialized.set_flags(flags | |
| SerializedVaultKeyset::TPM_WRAPPED | |
| SerializedVaultKeyset::SCRYPT_WRAPPED); |
| EXPECT_TRUE(mount_->StoreVaultKeysetForUser(user->obfuscated_username, |
| 0, &serialized)); |
| |
| // When we call DecryptVaultKeyset, it should re-encrypt |
| // the keys and write with only one flag set |
| error = MOUNT_ERROR_NONE; |
| user->InjectKeyset(&platform_, true); |
| |
| EXPECT_TRUE(mount_->DecryptVaultKeyset(credentials, &vault_keyset, |
| &serialized, &key_index, &error)); |
| ASSERT_EQ(error, MOUNT_ERROR_NONE); |
| ASSERT_NE(migrated_keyset.size(), 0); |
| |
| flags = serialized.flags(); |
| ASSERT_EQ(SerializedVaultKeyset::TPM_WRAPPED, |
| (flags & SerializedVaultKeyset::TPM_WRAPPED)); |
| ASSERT_EQ(0, (flags & SerializedVaultKeyset::SCRYPT_WRAPPED)); |
| ASSERT_EQ(SerializedVaultKeyset::SCRYPT_DERIVED, |
| (flags & SerializedVaultKeyset::SCRYPT_DERIVED)); |
| } |
| |
| TEST_P(MountTest, CreateTrackedSubdirectories) { |
| EXPECT_TRUE(DoMountInit()); |
| InsertTestUsers(&kDefaultUsers[0], 1); |
| TestUser *user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| FilePath dest_dir; |
| if (ShouldTestEcryptfs()) { |
| dest_dir = user->vault_path; |
| mount_->mount_type_ = ::cryptohome::MountType::ECRYPTFS; |
| } else { |
| dest_dir = user->vault_mount_path; |
| mount_->mount_type_ = ::cryptohome::MountType::DIR_CRYPTO; |
| } |
| EXPECT_CALL(platform_, DirectoryExists(dest_dir)) |
| .WillOnce(Return(true)); |
| |
| // Expectations for each tracked subdirectory. |
| for (const auto& tracked_dir : MountHelper::GetTrackedSubdirectories()) { |
| const FilePath tracked_dir_path = dest_dir.Append(tracked_dir); |
| EXPECT_CALL(platform_, DirectoryExists(tracked_dir_path)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, CreateDirectory(tracked_dir_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL( |
| platform_, |
| SetOwnership(tracked_dir_path, chronos_uid_, chronos_gid_, true)) |
| .WillOnce(Return(true)); |
| if (!ShouldTestEcryptfs()) { |
| // For dircrypto, xattr should be set. |
| EXPECT_CALL(platform_, SetExtendedFileAttribute( |
| tracked_dir_path, |
| kTrackedDirectoryNameAttribute, |
| StrEq(tracked_dir_path.BaseName().value()), |
| tracked_dir_path.BaseName().value().size())).WillOnce(Return(true)); |
| } |
| } |
| // Run the method. |
| EXPECT_TRUE( |
| mount_->CreateTrackedSubdirectories(credentials, true /* is_new */)); |
| } |
| |
| TEST_P(MountTest, CreateTrackedSubdirectoriesReplaceExistingDir) { |
| EXPECT_TRUE(DoMountInit()); |
| InsertTestUsers(&kDefaultUsers[0], 1); |
| TestUser *user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| FilePath dest_dir; |
| if (ShouldTestEcryptfs()) { |
| dest_dir = user->vault_path; |
| mount_->mount_type_ = ::cryptohome::MountType::ECRYPTFS; |
| } else { |
| dest_dir = user->vault_mount_path; |
| mount_->mount_type_ = ::cryptohome::MountType::DIR_CRYPTO; |
| } |
| EXPECT_CALL(platform_, DirectoryExists(dest_dir)) |
| .WillOnce(Return(true)); |
| |
| // Expectations for each tracked subdirectory. |
| for (const auto& tracked_dir : MountHelper::GetTrackedSubdirectories()) { |
| const FilePath tracked_dir_path = dest_dir.Append(tracked_dir); |
| const FilePath userside_dir = user->vault_mount_path.Append(tracked_dir); |
| // Simulate the case there already exists a non-passthrough-dir |
| if (ShouldTestEcryptfs()) { |
| // For ecryptfs, delete and replace the existing directory. |
| EXPECT_CALL(platform_, DirectoryExists(userside_dir)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DeleteFile(userside_dir, true)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(tracked_dir_path)) |
| .WillOnce(Return(false)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, CreateDirectory(tracked_dir_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL( |
| platform_, |
| SetOwnership(tracked_dir_path, chronos_uid_, chronos_gid_, true)) |
| .WillOnce(Return(true)); |
| } else { |
| // For dircrypto, just skip the directory creation. |
| EXPECT_CALL(platform_, DirectoryExists(tracked_dir_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, SetExtendedFileAttribute( |
| tracked_dir_path, |
| kTrackedDirectoryNameAttribute, |
| StrEq(tracked_dir_path.BaseName().value()), |
| tracked_dir_path.BaseName().value().size())).WillOnce(Return(true)); |
| } |
| } |
| // Run the method. |
| EXPECT_TRUE( |
| mount_->CreateTrackedSubdirectories(credentials, false /* is_new */)); |
| } |
| |
| TEST_P(MountTest, MountCryptohomePreviousMigrationIncomplete) { |
| // Checks that if both ecryptfs and dircrypto home directories |
| // exist, fails with an error. |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| // Prepare a dummy user and a key. |
| InsertTestUsers(&kDefaultUsers[10], 1); |
| TestUser* user = &helper_.users[0]; |
| user->InjectKeyset(&platform_, true); |
| Credentials credentials(user->username, user->passkey); |
| |
| // Not legacy |
| EXPECT_CALL(platform_, FileExists(user->image_path)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, CreateDirectory(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| |
| // Mock the situation that both types of data directory exists. |
| EXPECT_CALL(platform_, |
| DirectoryExists(AnyOf(user->vault_path, user->vault_mount_path, |
| user->user_vault_path))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, GetDirCryptoKeyState(user->vault_mount_path)) |
| .WillRepeatedly(Return(dircrypto::KeyState::ENCRYPTED)); |
| |
| MountError error = MOUNT_ERROR_NONE; |
| ASSERT_FALSE( |
| mount_->MountCryptohome(credentials, GetDefaultMountArgs(), &error)); |
| ASSERT_EQ(MOUNT_ERROR_PREVIOUS_MIGRATION_INCOMPLETE, error); |
| } |
| |
| TEST_P(MountTest, MountCryptohomeToMigrateFromEcryptfs) { |
| // Checks that to_migrate_from_ecryptfs option is handled correctly. |
| // When the existing vault is ecryptfs, mount it to a temporary location while |
| // setting up a new dircrypto directory. |
| // When the existing vault is dircrypto, just fail. |
| InsertTestUsers(&kDefaultUsers[10], 1); |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| TestUser *user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| user->InjectKeyset(&platform_, ShouldTestEcryptfs()); |
| |
| // Inject dircrypto user paths. |
| user->InjectUserPaths(&platform_, chronos_uid_, chronos_gid_, shared_gid_, |
| kDaemonGid, false /* is_ecryptfs */); |
| |
| if (ShouldTestEcryptfs()) { |
| // Inject user ecryptfs paths too. |
| user->InjectUserPaths(&platform_, chronos_uid_, chronos_gid_, shared_gid_, |
| kDaemonGid, true /* is_ecryptfs */); |
| |
| // When an ecryptfs vault exists, mount it to a temporary location. |
| FilePath temporary_mount = user->base_path.Append(kTemporaryMountDir); |
| EXPECT_CALL(platform_, CreateDirectory(temporary_mount)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, Mount(user->vault_path, temporary_mount, |
| "ecryptfs", kDefaultMountFlags, _)) |
| .WillOnce(Return(true)); |
| |
| // Key set up for both dircrypto and ecryptfs. |
| ExpectCryptohomeKeySetupForDircrypto(*user); |
| ExpectCryptohomeKeySetupForEcryptfs(*user); |
| |
| EXPECT_CALL(platform_, DirectoryExists(user->vault_path)) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(platform_, IsDirectoryMounted(user->vault_mount_path)) |
| .WillOnce(Return(false)); |
| |
| EXPECT_CALL(platform_, CreateDirectory(user->vault_mount_path)) |
| .WillRepeatedly(Return(true)); |
| } |
| |
| EXPECT_CALL(platform_, |
| CreateDirectory(MountHelper::GetNewUserPath(user->username))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| |
| MountError error = MOUNT_ERROR_NONE; |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.to_migrate_from_ecryptfs = true; |
| if (ShouldTestEcryptfs()) { |
| EXPECT_TRUE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| } else { |
| // Fail if the existing vault is not ecryptfs. |
| EXPECT_FALSE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| } |
| } |
| |
| TEST_P(MountTest, MountCryptohomeShadowOnly) { |
| // Checks that the shadow_only option is handled correctly. |
| InsertTestUsers(&kDefaultUsers[10], 1); |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| TestUser* user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| user->InjectKeyset(&platform_, true); |
| |
| // Inject dircrypto user paths. |
| user->InjectUserPaths(&platform_, chronos_uid_, chronos_gid_, shared_gid_, |
| kDaemonGid, ShouldTestEcryptfs()); |
| |
| ExpectCryptohomeMountShadowOnly(*user); |
| |
| MountError error = MOUNT_ERROR_NONE; |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.shadow_only = true; |
| EXPECT_TRUE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| } |
| |
| TEST_P(MountTest, MountCryptohomeForceDircrypto) { |
| // Checks that the force-dircrypto flag correctly rejects to mount ecryptfs. |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| EXPECT_TRUE(DoMountInit()); |
| |
| // Prepare a dummy user and a key. |
| InsertTestUsers(&kDefaultUsers[10], 1); |
| TestUser* user = &helper_.users[0]; |
| user->InjectKeyset(&platform_, true); |
| user->InjectUserPaths(&platform_, chronos_uid_, chronos_gid_, shared_gid_, |
| kDaemonGid, ShouldTestEcryptfs()); |
| |
| EXPECT_CALL(platform_, CreateDirectory(_)) |
| .WillRepeatedly(Return(true)); |
| |
| // Mock setup for successful mount when dircrypto is tested. |
| if (!ShouldTestEcryptfs()) { |
| ExpectCryptohomeMount(*user); |
| |
| // Expectations for tracked subdirectories |
| EXPECT_CALL(platform_, DirectoryExists( |
| Property(&FilePath::value, StartsWith(user->vault_mount_path.value())))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetExtendedFileAttribute( |
| Property(&FilePath::value, StartsWith(user->vault_mount_path.value())), |
| _, _, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists( |
| Property(&FilePath::value, StartsWith(user->vault_mount_path.value())))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetGroupAccessible( |
| Property(&FilePath::value, StartsWith(user->vault_mount_path.value())), |
| _, _)) |
| .WillRepeatedly(Return(true)); |
| } |
| |
| Credentials credentials(user->username, user->passkey); |
| |
| MountError error = MOUNT_ERROR_NONE; |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.force_dircrypto = true; |
| |
| if (ShouldTestEcryptfs()) { |
| // Should reject mounting ecryptfs vault. |
| EXPECT_FALSE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| EXPECT_EQ(MOUNT_ERROR_OLD_ENCRYPTION, error); |
| } else { |
| // Should succeed in mounting in dircrypto. |
| EXPECT_TRUE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| EXPECT_EQ(MOUNT_ERROR_NONE, error); |
| } |
| } |
| |
| // Test setup that initially has no cryptohomes. |
| const TestUserInfo kNoUsers[] = { |
| {"user0@invalid.domain", "zero", false}, |
| {"user1@invalid.domain", "odin", false}, |
| {"user2@invalid.domain", "dwaa", false}, |
| {"owner@invalid.domain", "1234", false}, |
| }; |
| const int kNoUserCount = base::size(kNoUsers); |
| |
| // Test setup that initially has a cryptohome for the owner only. |
| const TestUserInfo kOwnerOnlyUsers[] = { |
| {"user0@invalid.domain", "zero", false}, |
| {"user1@invalid.domain", "odin", false}, |
| {"user2@invalid.domain", "dwaa", false}, |
| {"owner@invalid.domain", "1234", true}, |
| }; |
| const int kOwnerOnlyUserCount = base::size(kOwnerOnlyUsers); |
| |
| // Test setup that initially has cryptohomes for all users. |
| const TestUserInfo kAlternateUsers[] = { |
| {"user0@invalid.domain", "zero", true}, |
| {"user1@invalid.domain", "odin", true}, |
| {"user2@invalid.domain", "dwaa", true}, |
| {"owner@invalid.domain", "1234", true}, |
| }; |
| const int kAlternateUserCount = base::size(kAlternateUsers); |
| |
| class AltImageTest : public MountTest { |
| public: |
| AltImageTest() {} |
| ~AltImageTest() { |
| MountTest::TearDown(); |
| } |
| |
| void SetUpAltImage(const TestUserInfo *users, int user_count) { |
| // Set up fresh users. |
| MountTest::SetUp(); |
| InsertTestUsers(users, user_count); |
| |
| EXPECT_CALL(platform_, DirectoryExists(kImageDir)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_TRUE(DoMountInit()); |
| } |
| |
| // Set the user with specified |key_file| old. |
| bool SetUserTimestamp(TestUser* user, base::Time timestamp) { |
| SerializedVaultKeyset serialized; |
| if (!LoadSerializedKeyset(user->credentials, &serialized)) { |
| LOG(ERROR) << "Failed to parse keyset for " |
| << user->username; |
| return false; |
| } |
| serialized.set_last_activity_timestamp(timestamp.ToInternalValue()); |
| bool ok = StoreSerializedKeyset(serialized, user); |
| if (!ok) { |
| LOG(ERROR) << "Failed to serialize new timestamp'd keyset for " |
| << user->username; |
| } |
| return ok; |
| } |
| |
| void PrepareHomedirs(bool inject_keyset, |
| const std::vector<int>* delete_vaults, |
| const std::vector<int>* mounted_vaults) { |
| bool populate_vaults = (vaults_.size() == 0); |
| // const string contents = "some encrypted contents"; |
| for (int user = 0; user != static_cast<int>(helper_.users.size()); user++) { |
| // Let their Cache dirs be filled with some data. |
| // Guarded to keep this function reusable. |
| if (populate_vaults) { |
| EXPECT_CALL(platform_, |
| DirectoryExists( |
| Property(&FilePath::value, |
| StartsWith(helper_.users[user].base_path.value())))) |
| .WillRepeatedly(Return(true)); |
| vaults_.push_back(helper_.users[user].base_path); |
| } |
| bool delete_user = false; |
| if (delete_vaults && delete_vaults->size() != 0) { |
| if (std::find(delete_vaults->begin(), delete_vaults->end(), user) != |
| delete_vaults->end()) |
| delete_user = true; |
| } |
| bool mounted_user = false; |
| if (mounted_vaults && mounted_vaults->size() != 0) { |
| if (std::find(mounted_vaults->begin(), mounted_vaults->end(), user) != |
| mounted_vaults->end()) |
| mounted_user = true; |
| } |
| |
| // After Cache & GCache are depleted. Users are deleted. To do so cleanly, |
| // their keysets timestamps are read into an in-memory. |
| if (inject_keyset && !mounted_user) |
| helper_.users[user].InjectKeyset(&platform_, false); |
| if (delete_user) { |
| EXPECT_CALL(platform_, |
| DeleteFile(helper_.users[user].base_path, true)) |
| .WillOnce(Return(true)); |
| } |
| } |
| } |
| |
| std::vector<FilePath> vaults_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AltImageTest); |
| }; |
| |
| class EphemeralNoUserSystemTest : public AltImageTest { |
| public: |
| EphemeralNoUserSystemTest() { } |
| |
| void SetUp() { |
| SetUpAltImage(kNoUsers, kNoUserCount); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(EphemeralNoUserSystemTest); |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(WithEcryptfs, EphemeralNoUserSystemTest, |
| ::testing::Values(true)); |
| INSTANTIATE_TEST_SUITE_P(WithDircrypto, EphemeralNoUserSystemTest, |
| ::testing::Values(false)); |
| |
| TEST_P(EphemeralNoUserSystemTest, CreateMyFilesDownloads) { |
| // Checks that MountHelper::SetUpEphemeralCryptohome creates |
| // MyFiles/Downloads. |
| const FilePath base_path("/ephemeral_home/"); |
| const FilePath downloads_path = base_path.Append("Downloads"); |
| const FilePath myfiles_path = base_path.Append("MyFiles"); |
| const FilePath myfiles_downloads_path = myfiles_path.Append("Downloads"); |
| const FilePath gcache_path = base_path.Append("GCache"); |
| const FilePath gcache_v2_path = base_path.Append("GCache").Append("v2"); |
| const auto gcache_dirs = Property( |
| &FilePath::value, StartsWith(base_path.Append("GCache").value())); |
| |
| // Expecting Downloads to not exist and then be created. |
| EXPECT_CALL(platform_, DirectoryExists(downloads_path)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, CreateDirectory(downloads_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| SetOwnership(downloads_path, chronos_uid_, chronos_gid_, _)) |
| .WillOnce(Return(true)); |
| // Expecting MyFiles to not exist and then be created. |
| EXPECT_CALL(platform_, DirectoryExists(myfiles_path)).WillOnce(Return(false)); |
| EXPECT_CALL(platform_, CreateDirectory(myfiles_path)).WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| SetOwnership(myfiles_path, chronos_uid_, chronos_gid_, _)) |
| .WillOnce(Return(true)); |
| // Expecting MyFiles/Downloads to not exist and then be created, with right |
| // user and group. |
| EXPECT_CALL(platform_, DirectoryExists(myfiles_downloads_path)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, CreateDirectory(myfiles_downloads_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, SetOwnership(myfiles_downloads_path, chronos_uid_, |
| chronos_gid_, _)) |
| .WillOnce(Return(true)); |
| |
| // Expect GCache and Gcache/v2 to be created with the right user and group. |
| EXPECT_CALL(platform_, DirectoryExists(gcache_path)).WillOnce(Return(false)); |
| EXPECT_CALL(platform_, CreateDirectory(gcache_path)).WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| SetOwnership(gcache_path, chronos_uid_, chronos_gid_, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(gcache_v2_path)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, CreateDirectory(gcache_v2_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| SetOwnership(gcache_v2_path, chronos_uid_, chronos_gid_, _)) |
| .WillOnce(Return(true)); |
| |
| EXPECT_CALL(platform_, SetOwnership(base_path, chronos_uid_, shared_gid_, _)) |
| .WillOnce(Return(true)); |
| |
| // Expectaction for Mount::SetupGroupAccess |
| // These files should exist. Then get group accessible called on them. |
| EXPECT_CALL(platform_, |
| FileExists(AnyOf(base_path, myfiles_path, downloads_path, |
| myfiles_downloads_path, gcache_dirs))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| SetGroupAccessible(AnyOf(base_path, myfiles_path, downloads_path, |
| myfiles_downloads_path, gcache_dirs), |
| shared_gid_, _)) |
| .WillRepeatedly(Return(true)); |
| |
| MountHelper mnt_helper(chronos_uid_, chronos_gid_, shared_gid_, kImageDir, |
| kSkelDir, helper_.system_salt, true /*legacy_mount*/, |
| &platform_); |
| |
| ASSERT_TRUE(mnt_helper.SetUpEphemeralCryptohome(base_path)); |
| } |
| |
| TEST_P(EphemeralNoUserSystemTest, CreateMyFilesDownloadsAlreadyExists) { |
| // Checks that MountHelper::SetUpEphemeralCryptohome doesn't re-recreate if |
| // already exists, just sets the ownership and group access for |base_path|. |
| const FilePath base_path("/ephemeral_home/"); |
| const FilePath downloads_path = base_path.Append("Downloads"); |
| const FilePath myfiles_path = base_path.Append("MyFiles"); |
| const FilePath myfiles_downloads_path = myfiles_path.Append("Downloads"); |
| const FilePath gcache_path = base_path.Append("GCache"); |
| const FilePath gcache_v2_path = base_path.Append("GCache").Append("v2"); |
| const auto gcache_dirs = Property( |
| &FilePath::value, StartsWith(base_path.Append("GCache").value())); |
| |
| // Expecting Downloads and MyFiles/Downloads to exist thus CreateDirectory |
| // isn't called. |
| EXPECT_CALL(platform_, DirectoryExists(downloads_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(myfiles_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(myfiles_downloads_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(gcache_path)).WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(gcache_v2_path)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, SetOwnership(base_path, chronos_uid_, shared_gid_, _)) |
| .WillOnce(Return(true)); |
| |
| // Expectaction for Mount::SetupGroupAccess. |
| // These files should exist, then SetGroupAccessible is called on them. |
| EXPECT_CALL(platform_, |
| FileExists(AnyOf(base_path, myfiles_path, downloads_path, |
| myfiles_downloads_path, gcache_dirs))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| SetGroupAccessible(AnyOf(base_path, myfiles_path, downloads_path, |
| myfiles_downloads_path, gcache_dirs), |
| shared_gid_, _)) |
| .WillRepeatedly(Return(true)); |
| |
| MountHelper mnt_helper(chronos_uid_, chronos_gid_, shared_gid_, kImageDir, |
| kSkelDir, helper_.system_salt, true /*legacy_mount*/, |
| &platform_); |
| |
| ASSERT_TRUE(mnt_helper.SetUpEphemeralCryptohome(base_path)); |
| } |
| |
| TEST_P(EphemeralNoUserSystemTest, OwnerUnknownMountCreateTest) { |
| // Checks that when a device is not enterprise enrolled and does not have a |
| // known owner, a regular vault is created and mounted. |
| set_policy(false, "", true); |
| |
| TestUser *user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| EXPECT_CALL(platform_, FileExists(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(user->image_path)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, DirectoryExists(user->vault_path)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, DirectoryExists(user->vault_mount_path)) |
| .WillRepeatedly(Return(false)); |
| ExpectCryptohomeKeySetup(*user); |
| EXPECT_CALL(platform_, CreateDirectory(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetOwnership(_, _, _, _)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetPermissions(_, _)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, WriteFileAtomicDurable(user->keyset_path, _, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, ReadFile(user->keyset_path, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(user->credentials), |
| Return(true))); |
| EXPECT_CALL(platform_, |
| DirectoryExists( |
| Property(&FilePath::value, |
| StartsWith(user->user_vault_path.value())))) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(platform_, Mount(_, _, kEphemeralMountType, |
| kDefaultMountFlags, _)).Times(0); |
| EXPECT_CALL(platform_, Mount(_, _, _, kDefaultMountFlags, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, Bind(_, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, IsDirectoryMounted(user->vault_mount_path)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, IsDirectoryMounted(FilePath("/home/chronos/user"))) |
| .WillOnce(Return(false)); |
| ExpectDownloadsBindMounts(*user); |
| ExpectDaemonStoreMounts(*user, false /* is_ephemeral */); |
| |
| // First user to login -> an owner. |
| EXPECT_CALL(tpm_, SetUserType(Tpm::UserType::Owner)) |
| .WillOnce(Return(true)); |
| |
| user->InjectKeyset(&platform_, true); |
| |
| EXPECT_CALL(platform_, GetFileEnumerator(kSkelDir, _, _)) |
| .WillOnce(Return(new NiceMock<MockFileEnumerator>())) |
| .WillOnce(Return(new NiceMock<MockFileEnumerator>())); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| MountError error; |
| ASSERT_TRUE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| |
| // Unmount succeeds. |
| ON_CALL(platform_, Unmount(_, _, _)).WillByDefault(Return(true)); |
| |
| // Unmount triggers setting user type to non-owner. |
| testing::Mock::VerifyAndClearExpectations(&tpm_); |
| EXPECT_CALL(tpm_, SetUserType(Tpm::UserType::NonOwner)) |
| .WillOnce(Return(true)); |
| |
| ASSERT_TRUE(mount_->UnmountCryptohome()); |
| } |
| |
| TEST_P(EphemeralNoUserSystemTest, MountSetUserTypeFailTest) { |
| // Checks that when a device is not enterprise enrolled and does not have a |
| // known owner, a regular vault is created and mounted. |
| set_policy(false, "", true); |
| |
| TestUser *user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| EXPECT_CALL(platform_, FileExists(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(user->image_path)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, DirectoryExists(_)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, DirectoryExists(user->vault_path)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, DirectoryExists(user->vault_mount_path)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, GetFileEnumerator(_, _, _)) |
| .WillOnce(Return(new NiceMock<MockFileEnumerator>())) |
| .WillOnce(Return(new NiceMock<MockFileEnumerator>())); |
| EXPECT_CALL(platform_, SetOwnership(_, _, _, _)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetPermissions(_, _)).WillRepeatedly(Return(true)); |
| |
| if (ShouldTestEcryptfs()) { |
| EXPECT_CALL(platform_, AddEcryptfsAuthToken(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| } else { |
| const key_serial_t kDirCryptoKeyId = 12345; |
| EXPECT_CALL(platform_, AddDirCryptoKeyToKeyring(_, _, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(kDirCryptoKeyId), Return(true))); |
| EXPECT_CALL(platform_, SetDirCryptoKey(user->vault_mount_path, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, InvalidateDirCryptoKey(kDirCryptoKeyId, kImageDir)) |
| .WillRepeatedly(Return(true)); |
| } |
| |
| EXPECT_CALL(platform_, CreateDirectory(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, WriteFileAtomicDurable(user->keyset_path, _, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, ReadFile(user->keyset_path, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(user->credentials), |
| Return(true))); |
| EXPECT_CALL(platform_, |
| DirectoryExists( |
| Property(&FilePath::value, StartsWith(user->user_vault_path.value())))) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(platform_, Mount(_, _, kEphemeralMountType, |
| kDefaultMountFlags, _)).Times(0); |
| EXPECT_CALL(platform_, Mount(_, _, _, kDefaultMountFlags, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, Bind(_, _)) |
| .WillRepeatedly(Return(true)); |
| |
| // Inject the failure. |
| // In case of MOUNT_ERROR_TPM_COMM_ERROR, it will retry mounting once, |
| // so an attempt to SetUserType will happen twice. |
| EXPECT_CALL(tpm_, SetUserType(_)) |
| .Times(2) |
| .WillRepeatedly(Return(false)); |
| |
| // Keyset enumeration and skeleton walk will be repeated twice due to mount |
| // retry logic. Note that InSequence is used here, as otherwise a single |
| // series of mocks will be triggered twice and fail due to over-saturation. |
| { |
| InSequence s; |
| for (int i = 0; i < 2; ++i) { |
| user->InjectKeyset(&platform_, true); |
| EXPECT_CALL(platform_, GetFileEnumerator(kSkelDir, _, _)) |
| .WillOnce(Return(new NiceMock<MockFileEnumerator>())) |
| .WillOnce(Return(new NiceMock<MockFileEnumerator>())); |
| } |
| } |
| |
| // Unmount succeeds. |
| ON_CALL(platform_, Unmount(_, _, _)).WillByDefault(Return(true)); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| MountError error; |
| ASSERT_FALSE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| ASSERT_EQ(MOUNT_ERROR_TPM_COMM_ERROR, error); |
| } |
| |
| // TODO(wad) Duplicate these tests with multiple mounts instead of one. |
| |
| TEST_P(EphemeralNoUserSystemTest, EnterpriseMountNoCreateTest) { |
| // Checks that when a device is enterprise enrolled, a tmpfs cryptohome is |
| // mounted and no regular vault is created. |
| set_policy(false, "", true); |
| mount_->set_enterprise_owned(true); |
| TestUser *user = &helper_.users[0]; |
| |
| // Always removes non-owner cryptohomes. |
| std::vector<FilePath> empty; |
| EXPECT_CALL(platform_, EnumerateDirectoryEntries(_, _, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(empty), Return(true))); |
| |
| EXPECT_CALL(platform_, Unmount(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| |
| ExpectEphemeralCryptohomeMount(*user); |
| |
| // Enterprise enrolled -> no one is the owner. |
| EXPECT_CALL(tpm_, SetUserType(Tpm::UserType::NonOwner)) |
| .WillOnce(Return(true)); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| Credentials credentials(user->username, user->passkey); |
| ASSERT_TRUE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| |
| // Detach succeeds. |
| ON_CALL(platform_, DetachLoop(_)).WillByDefault(Return(true)); |
| |
| // Implicit unmount triggers setting user type to non-owner. |
| testing::Mock::VerifyAndClearExpectations(&tpm_); |
| EXPECT_CALL(tpm_, SetUserType(Tpm::UserType::NonOwner)) |
| .WillOnce(Return(true)); |
| } |
| |
| TEST_P(EphemeralNoUserSystemTest, OwnerUnknownMountIsEphemeralTest) { |
| // Checks that when a device is not enterprise enrolled and does not have a |
| // known owner, a mount request with the |ensure_ephemeral| flag set fails. |
| TestUser* user = &helper_.users[0]; |
| |
| EXPECT_CALL(platform_, Mount(_, _, _, kDefaultMountFlags, _)).Times(0); |
| EXPECT_CALL(tpm_, SetUserType(_)).Times(0); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| Credentials credentials(user->username, user->passkey); |
| ASSERT_FALSE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| ASSERT_EQ(MOUNT_ERROR_EPHEMERAL_MOUNT_BY_OWNER, error); |
| } |
| |
| TEST_P(EphemeralNoUserSystemTest, EnterpriseMountIsEphemeralTest) { |
| // Checks that when a device is enterprise enrolled, a mount request with the |
| // |is_ephemeral| flag set causes a tmpfs cryptohome to be mounted and no |
| // regular vault to be created. |
| set_policy(true, "", false); |
| mount_->set_enterprise_owned(true); |
| TestUser *user = &helper_.users[0]; |
| |
| // Always removes non-owner cryptohomes. |
| std::vector<FilePath> empty; |
| EXPECT_CALL(platform_, EnumerateDirectoryEntries(_, _, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(empty), Return(true))); |
| |
| ExpectEphemeralCryptohomeMount(*user); |
| |
| // Enterprise enrolled -> no one is the owner. |
| EXPECT_CALL(tpm_, SetUserType(Tpm::UserType::NonOwner)) |
| .WillOnce(Return(true)); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| Credentials credentials(user->username, user->passkey); |
| ASSERT_TRUE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| |
| EXPECT_CALL(platform_, DetachLoop(kLoopDevice)).WillOnce(Return(true)); |
| EXPECT_CALL(platform_, Unmount(user->ephemeral_mount_path, _, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL( |
| platform_, |
| Unmount(Property(&FilePath::value, StartsWith("/home/chronos/u-")), _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL( |
| platform_, |
| Unmount(Property(&FilePath::value, StartsWith("/home/user/")), _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL( |
| platform_, |
| Unmount(Property(&FilePath::value, StartsWith("/home/root/")), _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL(platform_, Unmount(FilePath("/home/chronos/user"), _, _)) |
| .WillOnce(Return(true)); // legacy mount |
| EXPECT_CALL(platform_, Unmount(Property(&FilePath::value, |
| StartsWith(kRunDaemonStoreBaseDir)), |
| _, _)) |
| .WillOnce(Return(true)); // daemon store mounts |
| EXPECT_CALL(platform_, ClearUserKeyring()).WillRepeatedly(Return(true)); |
| |
| ExpectDownloadsUnmounts(*user); |
| |
| // Unmount triggers setting user type to non-owner. |
| testing::Mock::VerifyAndClearExpectations(&tpm_); |
| EXPECT_CALL(tpm_, SetUserType(Tpm::UserType::NonOwner)) |
| .WillOnce(Return(true)); |
| |
| EXPECT_TRUE(mount_->UnmountCryptohome()); |
| } |
| |
| TEST_P(EphemeralNoUserSystemTest, EnterpriseMountStatVFSFailure) { |
| // Checks the case when ephemeral statvfs call fails. |
| set_policy(false, "", true); |
| mount_->set_enterprise_owned(true); |
| const TestUser* const user = &helper_.users[0]; |
| |
| EXPECT_CALL(platform_, DetachLoop(_)).Times(0); |
| ExpectCryptohomeRemoval(*user); |
| |
| EXPECT_CALL(platform_, StatVFS(FilePath(kEphemeralCryptohomeDir), _)) |
| .WillOnce(Return(false)); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| Credentials credentials(user->username, user->passkey); |
| ASSERT_FALSE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| } |
| |
| TEST_P(EphemeralNoUserSystemTest, EnterpriseMountCreateSparseDirFailure) { |
| // Checks the case when directory for ephemeral sparse files fails to be |
| // created. |
| set_policy(false, "", true); |
| mount_->set_enterprise_owned(true); |
| const TestUser* const user = &helper_.users[0]; |
| |
| EXPECT_CALL(platform_, DetachLoop(_)).Times(0); |
| ExpectCryptohomeRemoval(*user); |
| |
| EXPECT_CALL(platform_, StatVFS(FilePath(kEphemeralCryptohomeDir), _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, CreateDirectory(MountHelper::GetEphemeralSparseFile( |
| user->obfuscated_username) |
| .DirName())) |
| .WillOnce(Return(false)); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| Credentials credentials(user->username, user->passkey); |
| ASSERT_FALSE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| } |
| |
| TEST_P(EphemeralNoUserSystemTest, EnterpriseMountCreateSparseFailure) { |
| // Checks the case when ephemeral sparse file fails to create. |
| set_policy(false, "", true); |
| mount_->set_enterprise_owned(true); |
| const TestUser* const user = &helper_.users[0]; |
| const FilePath ephemeral_filename = |
| MountHelper::GetEphemeralSparseFile(user->obfuscated_username); |
| |
| EXPECT_CALL(platform_, DetachLoop(_)).Times(0); |
| EXPECT_CALL(platform_, DeleteFile(ephemeral_filename, _)).Times(1); |
| ExpectCryptohomeRemoval(*user); |
| |
| EXPECT_CALL(platform_, StatVFS(FilePath(kEphemeralCryptohomeDir), _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, CreateDirectory(ephemeral_filename.DirName())) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, CreateSparseFile(ephemeral_filename, _)) |
| .WillOnce(Return(false)); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| Credentials credentials(user->username, user->passkey); |
| ASSERT_FALSE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| } |
| |
| TEST_P(EphemeralNoUserSystemTest, EnterpriseMountAttachLoopFailure) { |
| // Checks that when ephemeral loop device fails to attach, clean up happens |
| // appropriately. |
| set_policy(false, "", true); |
| mount_->set_enterprise_owned(true); |
| const TestUser* const user = &helper_.users[0]; |
| const FilePath ephemeral_filename = |
| MountHelper::GetEphemeralSparseFile(user->obfuscated_username); |
| |
| EXPECT_CALL(platform_, DetachLoop(_)).Times(0); |
| EXPECT_CALL(platform_, DeleteFile(ephemeral_filename, _)).Times(1); |
| ExpectCryptohomeRemoval(*user); |
| |
| EXPECT_CALL(platform_, StatVFS(FilePath(kEphemeralCryptohomeDir), _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, CreateDirectory(ephemeral_filename.DirName())) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, CreateSparseFile(ephemeral_filename, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| FormatExt4(ephemeral_filename, kDefaultExt4FormatOpts, 0)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, AttachLoop(ephemeral_filename)) |
| .WillOnce(Return(FilePath())); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| Credentials credentials(user->username, user->passkey); |
| ASSERT_FALSE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| } |
| |
| TEST_P(EphemeralNoUserSystemTest, EnterpriseMountFormatFailure) { |
| // Checks that when ephemeral loop device fails to be formatted, clean up |
| // happens appropriately. |
| set_policy(false, "", true); |
| mount_->set_enterprise_owned(true); |
| const TestUser* const user = &helper_.users[0]; |
| const FilePath ephemeral_filename = |
| MountHelper::GetEphemeralSparseFile(user->obfuscated_username); |
| |
| EXPECT_CALL(platform_, DetachLoop(_)).Times(0); |
| EXPECT_CALL( |
| platform_, |
| DeleteFile(ephemeral_filename, |
| _)) |
| .Times(1); |
| ExpectCryptohomeRemoval(*user); |
| |
| EXPECT_CALL(platform_, StatVFS(FilePath(kEphemeralCryptohomeDir), _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, CreateDirectory(ephemeral_filename.DirName())) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, CreateSparseFile(ephemeral_filename, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| FormatExt4(ephemeral_filename, kDefaultExt4FormatOpts, 0)) |
| .WillOnce(Return(false)); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| Credentials credentials(user->username, user->passkey); |
| ASSERT_FALSE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| } |
| |
| TEST_P(EphemeralNoUserSystemTest, EnterpriseMountEnsureUserMountFailure) { |
| // Checks that when ephemeral mount fails to ensure mount points, clean up |
| // happens appropriately. |
| set_policy(false, "", true); |
| mount_->set_enterprise_owned(true); |
| const TestUser* const user = &helper_.users[0]; |
| const FilePath ephemeral_filename = |
| MountHelper::GetEphemeralSparseFile(user->obfuscated_username); |
| |
| EXPECT_CALL(platform_, DetachLoop(_)).Times(1); |
| EXPECT_CALL(platform_, DeleteFile(ephemeral_filename, _)).Times(1); |
| ExpectCryptohomeRemoval(*user); |
| |
| EXPECT_CALL(platform_, StatVFS(FilePath(kEphemeralCryptohomeDir), _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, CreateSparseFile(ephemeral_filename, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| FormatExt4(ephemeral_filename, kDefaultExt4FormatOpts, 0)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, AttachLoop(ephemeral_filename)) |
| .WillOnce(Return(FilePath("/dev/loop7"))); |
| EXPECT_CALL(platform_, Stat(_, _)).WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, CreateDirectory(_)).WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, CreateDirectory(ephemeral_filename.DirName())) |
| .WillOnce(Return(true)); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| Credentials credentials(user->username, user->passkey); |
| |
| // Detach succeeds. |
| ON_CALL(platform_, DetachLoop(_)).WillByDefault(Return(true)); |
| |
| ASSERT_FALSE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| } |
| |
| class EphemeralOwnerOnlySystemTest : public AltImageTest { |
| public: |
| EphemeralOwnerOnlySystemTest() { } |
| |
| void SetUp() { |
| SetUpAltImage(kOwnerOnlyUsers, kOwnerOnlyUserCount); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(EphemeralOwnerOnlySystemTest); |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(WithEcryptfs, EphemeralOwnerOnlySystemTest, |
| ::testing::Values(true)); |
| INSTANTIATE_TEST_SUITE_P(WithDircrypto, EphemeralOwnerOnlySystemTest, |
| ::testing::Values(false)); |
| |
| TEST_P(EphemeralOwnerOnlySystemTest, MountNoCreateTest) { |
| // Checks that when a device is not enterprise enrolled and has a known owner, |
| // a tmpfs cryptohome is mounted and no regular vault is created. |
| TestUser* owner = &helper_.users[3]; |
| TestUser* user = &helper_.users[0]; |
| set_policy(true, owner->username, true); |
| Credentials credentials(user->username, user->passkey); |
| |
| // Always removes non-owner cryptohomes. |
| std::vector<FilePath> owner_only; |
| owner_only.push_back(owner->base_path); |
| |
| EXPECT_CALL(platform_, EnumerateDirectoryEntries(_, _, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(owner_only), Return(true))); |
| |
| EXPECT_CALL(platform_, IsDirectoryMounted(_)) |
| .WillRepeatedly(Return(false)); |
| |
| ExpectEphemeralCryptohomeMount(*user); |
| |
| // Different user -> not an owner. |
| EXPECT_CALL(tpm_, SetUserType(Tpm::UserType::NonOwner)) |
| .WillOnce(Return(true)); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| ASSERT_TRUE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| |
| EXPECT_CALL(platform_, Unmount(user->ephemeral_mount_path, _, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| Unmount( |
| Property(&FilePath::value, StartsWith("/home/chronos/u-")), |
| _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL(platform_, |
| Unmount( |
| Property(&FilePath::value, StartsWith("/home/user/")), |
| _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL(platform_, |
| Unmount( |
| Property(&FilePath::value, StartsWith("/home/root/")), |
| _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL(platform_, Unmount(FilePath("/home/chronos/user"), _, _)) |
| .WillOnce(Return(true)); // legacy mount |
| EXPECT_CALL(platform_, Unmount(Property(&FilePath::value, |
| StartsWith(kRunDaemonStoreBaseDir)), |
| _, _)) |
| .WillOnce(Return(true)); // daemon store mounts |
| EXPECT_CALL(platform_, ClearUserKeyring()) |
| .WillRepeatedly(Return(true)); |
| |
| ExpectDownloadsUnmounts(*user); |
| |
| // Detach succeeds. |
| ON_CALL(platform_, DetachLoop(_)).WillByDefault(Return(true)); |
| |
| // Unmount triggers setting user type to non-owner. |
| testing::Mock::VerifyAndClearExpectations(&tpm_); |
| EXPECT_CALL(tpm_, SetUserType(Tpm::UserType::NonOwner)) |
| .WillOnce(Return(true)); |
| |
| ASSERT_TRUE(mount_->UnmountCryptohome()); |
| } |
| |
| TEST_P(EphemeralOwnerOnlySystemTest, NonOwnerMountIsEphemeralTest) { |
| // Checks that when a device is not enterprise enrolled and has a known owner, |
| // a mount request for a non-owner user with the |is_ephemeral| flag set |
| // causes a tmpfs cryptohome to be mounted and no regular vault to be created. |
| TestUser* owner = &helper_.users[3]; |
| TestUser* user = &helper_.users[0]; |
| set_policy(true, owner->username, false); |
| Credentials credentials(user->username, user->passkey); |
| |
| // Always removes non-owner cryptohomes. |
| std::vector<FilePath> owner_only; |
| owner_only.push_back(owner->base_path); |
| |
| EXPECT_CALL(platform_, EnumerateDirectoryEntries(_, _, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(owner_only), Return(true))); |
| |
| EXPECT_CALL(platform_, Unmount(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| ExpectEphemeralCryptohomeMount(*user); |
| |
| // Different user -> not an owner. |
| EXPECT_CALL(tpm_, SetUserType(Tpm::UserType::NonOwner)) |
| .WillOnce(Return(true)); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| ASSERT_TRUE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| |
| // Detach succeeds. |
| ON_CALL(platform_, DetachLoop(_)).WillByDefault(Return(true)); |
| |
| // Implicit unmount triggers setting user type to non-owner. |
| testing::Mock::VerifyAndClearExpectations(&tpm_); |
| EXPECT_CALL(tpm_, SetUserType(Tpm::UserType::NonOwner)) |
| .WillOnce(Return(true)); |
| } |
| |
| TEST_P(EphemeralOwnerOnlySystemTest, OwnerMountIsEphemeralTest) { |
| // Checks that when a device is not enterprise enrolled and has a known owner, |
| // a mount request for the owner with the |ensure_ephemeral| flag set fails. |
| TestUser* owner = &helper_.users[3]; |
| set_policy(true, owner->username, false); |
| Credentials credentials(owner->username, owner->passkey); |
| |
| EXPECT_CALL(platform_, Mount(_, _, _, kDefaultMountFlags, _)).Times(0); |
| EXPECT_CALL(tpm_, SetUserType(_)).Times(0); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| ASSERT_FALSE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| ASSERT_EQ(MOUNT_ERROR_EPHEMERAL_MOUNT_BY_OWNER, error); |
| } |
| |
| class EphemeralExistingUserSystemTest : public AltImageTest { |
| public: |
| EphemeralExistingUserSystemTest() { } |
| |
| void SetUp() { |
| SetUpAltImage(kAlternateUsers, kAlternateUserCount); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(EphemeralExistingUserSystemTest); |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(WithEcryptfs, EphemeralExistingUserSystemTest, |
| ::testing::Values(true)); |
| INSTANTIATE_TEST_SUITE_P(WithDircrypto, EphemeralExistingUserSystemTest, |
| ::testing::Values(false)); |
| |
| TEST_P(EphemeralExistingUserSystemTest, OwnerUnknownMountNoRemoveTest) { |
| // Checks that when a device is not enterprise enrolled and does not have a |
| // known owner, no stale cryptohomes are removed while mounting. |
| set_policy(false, "", true); |
| TestUser* user = &helper_.users[0]; |
| |
| // No c-homes will be removed. The rest of the mocking just gets us to |
| // Mount(). |
| for (auto& user : helper_.users) |
| user.InjectUserPaths(&platform_, chronos_uid_, chronos_gid_, |
| shared_gid_, kDaemonGid, ShouldTestEcryptfs()); |
| |
| std::vector<FilePath> empty; |
| EXPECT_CALL(platform_, EnumerateDirectoryEntries(_, _, _)) |
| .WillOnce(DoAll(SetArgPointee<2>(empty), Return(true))); |
| |
| EXPECT_CALL(platform_, Stat(_, _)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, CreateDirectory(user->vault_path)) |
| .Times(0); |
| EXPECT_CALL(platform_, CreateDirectory(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetOwnership(_, _, _, _)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetPermissions(_, _)) |
| .WillRepeatedly(Return(true)); |
| |
| ExpectCryptohomeMount(*user); |
| EXPECT_CALL(platform_, ClearUserKeyring()).WillOnce(Return(true)); |
| |
| EXPECT_CALL(platform_, SetGroupAccessible(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, DeleteFile(_, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(_)) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(platform_, Mount(_, _, kEphemeralMountType, |
| kDefaultMountFlags, _)).Times(0); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| MountError error; |
| user->InjectKeyset(&platform_, true); |
| Credentials credentials(user->username, user->passkey); |
| ASSERT_TRUE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| |
| EXPECT_CALL(platform_, Unmount(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| if (ShouldTestEcryptfs()) { |
| EXPECT_CALL(platform_, |
| Unmount(Property(&FilePath::value, EndsWith("/mount")), _, _)) |
| .WillOnce(Return(true)); // user mount |
| } |
| EXPECT_CALL(platform_, |
| Unmount( |
| Property(&FilePath::value, StartsWith("/home/chronos/u-")), |
| _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL(platform_, |
| Unmount( |
| Property(&FilePath::value, StartsWith("/home/user/")), |
| _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL(platform_, |
| Unmount( |
| Property(&FilePath::value, StartsWith("/home/root/")), |
| _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL(platform_, Unmount(FilePath("/home/chronos/user"), _, _)) |
| .WillOnce(Return(true)); // legacy mount |
| EXPECT_CALL(platform_, Unmount(Property(&FilePath::value, |
| StartsWith(kRunDaemonStoreBaseDir)), |
| _, _)) |
| .WillOnce(Return(true)); // daemon store mounts |
| EXPECT_CALL(platform_, ClearUserKeyring()) |
| .WillRepeatedly(Return(true)); |
| ExpectDownloadsUnmounts(*user); |
| ASSERT_TRUE(mount_->UnmountCryptohome()); |
| } |
| |
| TEST_P(EphemeralExistingUserSystemTest, EnterpriseMountRemoveTest) { |
| // Checks that when a device is enterprise enrolled, all stale cryptohomes are |
| // removed while mounting. |
| set_policy(false, "", true); |
| mount_->set_enterprise_owned(true); |
| TestUser* user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| std::vector<int> expect_deletion; |
| expect_deletion.push_back(0); |
| expect_deletion.push_back(1); |
| expect_deletion.push_back(2); |
| expect_deletion.push_back(3); |
| PrepareHomedirs(true, &expect_deletion, NULL); |
| |
| // Let Mount know how many vaults there are. |
| std::vector<FilePath> no_vaults; |
| EXPECT_CALL(platform_, EnumerateDirectoryEntries(kImageDir, false, _)) |
| .WillOnce(DoAll(SetArgPointee<2>(vaults_), Return(true))) |
| // Don't re-delete on Unmount. |
| .WillRepeatedly(DoAll(SetArgPointee<2>(no_vaults), Return(true))); |
| // Don't say any cryptohomes are mounted |
| EXPECT_CALL(platform_, IsDirectoryMounted(_)) |
| .WillRepeatedly(Return(false)); |
| std::vector<FilePath> empty; |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries( |
| AnyOf( |
| FilePath("/home/root/"), |
| FilePath("/home/user/")), |
| _, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(empty), Return(true))); |
| EXPECT_CALL(platform_, |
| Stat(AnyOf(FilePath("/home/chronos"), |
| MountHelper::GetNewUserPath(user->username)), |
| _)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, |
| Stat(AnyOf(FilePath("/home"), |
| FilePath("/home/root"), |
| brillo::cryptohome::home::GetRootPath(user->username), |
| FilePath("/home/user"), |
| brillo::cryptohome::home::GetUserPath(user->username)), |
| _)) |
| .WillRepeatedly(Return(false)); |
| helper_.InjectEphemeralSkeleton(&platform_, |
| FilePath(user->user_ephemeral_mount_path)); |
| user->InjectUserPaths(&platform_, chronos_uid_, chronos_gid_, |
| shared_gid_, kDaemonGid, ShouldTestEcryptfs()); |
| // Only expect the mounted user to "exist". |
| EXPECT_CALL(platform_, |
| DirectoryExists( |
| Property(&FilePath::value, StartsWith(user->user_mount_path.value())))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, CreateDirectory(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetOwnership(_, _, _, _)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetPermissions(_, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetGroupAccessible(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| DeleteFile(MountHelper::GetEphemeralSparseFile(user->obfuscated_username), |
| _)) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(platform_, Stat(user->root_ephemeral_mount_path, _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries(user->ephemeral_mount_path, _, _)) |
| .WillOnce(DoAll(SetArgPointee<2>(empty), Return(true))); |
| EXPECT_CALL(platform_, DeleteFile(user->root_ephemeral_mount_path, true)) |
| .WillOnce(Return(true)); |
| |
| ExpectEphemeralCryptohomeMount(*user); |
| |
| // Deleting users will cause each user's shadow root subdir to be |
| // searched for LE credentials. |
| for (const auto& user : helper_.users) { |
| EXPECT_CALL( |
| platform_, |
| GetFileEnumerator(kImageDir.Append(user.obfuscated_username), false, _)) |
| .WillOnce(Return(new NiceMock<MockFileEnumerator>())); |
| } |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| ASSERT_TRUE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| |
| EXPECT_CALL(platform_, Unmount(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| Unmount( |
| Property(&FilePath::value, StartsWith("/home/chronos/u-")), |
| _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL(platform_, |
| Unmount( |
| Property(&FilePath::value, StartsWith("/home/user/")), |
| _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL(platform_, |
| Unmount( |
| Property(&FilePath::value, StartsWith("/home/root/")), |
| _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL(platform_, Unmount(FilePath("/home/chronos/user"), _, _)) |
| .WillOnce(Return(true)); // legacy mount |
| EXPECT_CALL(platform_, DeleteFile(user->ephemeral_mount_path, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, ClearUserKeyring()) |
| .WillRepeatedly(Return(true)); |
| ExpectDownloadsUnmounts(*user); |
| // Detach succeeds. |
| ON_CALL(platform_, DetachLoop(_)).WillByDefault(Return(true)); |
| ASSERT_TRUE(mount_->UnmountCryptohome()); |
| } |
| |
| TEST_P(EphemeralExistingUserSystemTest, MountRemoveTest) { |
| // Checks that when a device is not enterprise enrolled and has a known owner, |
| // all non-owner cryptohomes are removed while mounting. |
| TestUser* owner = &helper_.users[3]; |
| set_policy(true, owner->username, true); |
| TestUser* user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| std::vector<int> expect_deletion; |
| expect_deletion.push_back(0); // Mounting user shouldn't use be persistent. |
| expect_deletion.push_back(1); |
| expect_deletion.push_back(2); |
| // Expect all users but the owner to be removed. |
| PrepareHomedirs(true, &expect_deletion, NULL); |
| |
| // Let Mount know how many vaults there are. |
| std::vector<FilePath> no_vaults; |
| EXPECT_CALL(platform_, EnumerateDirectoryEntries(kImageDir, false, _)) |
| .WillOnce(DoAll(SetArgPointee<2>(vaults_), Return(true))) |
| // Don't re-delete on Unmount. |
| .WillRepeatedly(DoAll(SetArgPointee<2>(no_vaults), Return(true))); |
| // Don't say any cryptohomes are mounted |
| EXPECT_CALL(platform_, IsDirectoryMounted(_)) |
| .WillRepeatedly(Return(false)); |
| std::vector<FilePath> empty; |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries( |
| AnyOf(FilePath("/home/root/"), FilePath("/home/user/")), |
| _, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(empty), Return(true))); |
| EXPECT_CALL(platform_, |
| Stat(AnyOf(FilePath("/home/chronos"), |
| MountHelper::GetNewUserPath(user->username)), |
| _)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, |
| Stat(AnyOf(FilePath("/home"), |
| FilePath("/home/root"), |
| brillo::cryptohome::home::GetRootPath(user->username), |
| FilePath("/home/user"), |
| brillo::cryptohome::home::GetUserPath(user->username)), |
| _)) |
| .WillRepeatedly(Return(false)); |
| helper_.InjectEphemeralSkeleton(&platform_, |
| FilePath(user->user_ephemeral_mount_path)); |
| user->InjectUserPaths(&platform_, chronos_uid_, chronos_gid_, |
| shared_gid_, kDaemonGid, ShouldTestEcryptfs()); |
| // Only expect the mounted user to "exist". |
| EXPECT_CALL(platform_, |
| DirectoryExists( |
| Property(&FilePath::value, StartsWith(user->user_mount_path.value())))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, CreateDirectory(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetOwnership(_, _, _, _)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetPermissions(_, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetGroupAccessible(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL( |
| platform_, |
| DeleteFile(MountHelper::GetEphemeralSparseFile(user->obfuscated_username), |
| _)) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(platform_, Stat(user->root_ephemeral_mount_path, _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries(user->ephemeral_mount_path, _, _)) |
| .WillOnce(DoAll(SetArgPointee<2>(empty), Return(true))); |
| EXPECT_CALL(platform_, DeleteFile(user->root_ephemeral_mount_path, true)) |
| .WillOnce(Return(true)); |
| |
| ExpectEphemeralCryptohomeMount(*user); |
| |
| // Deleting users will cause "going-to-be-deleted" users' shadow root |
| // subdir to be searched for LE credentials. |
| for (int i = 0; i < helper_.users.size() - 1; i++) { |
| TestUser* cur_user = &helper_.users[i]; |
| EXPECT_CALL(platform_, |
| GetFileEnumerator( |
| kImageDir.Append(cur_user->obfuscated_username), false, _)) |
| .WillOnce(Return(new NiceMock<MockFileEnumerator>())); |
| } |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| ASSERT_TRUE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| |
| EXPECT_CALL(platform_, Unmount(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| Unmount( |
| Property(&FilePath::value, StartsWith("/home/chronos/u-")), |
| _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL(platform_, |
| Unmount(Property(&FilePath::value, StartsWith("/home/user/")), |
| _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL(platform_, |
| Unmount( |
| Property(&FilePath::value, StartsWith("/home/root/")), |
| _, _)) |
| .WillOnce(Return(true)); // user mount |
| EXPECT_CALL(platform_, Unmount(FilePath("/home/chronos/user"), _, _)) |
| .WillOnce(Return(true)); // legacy mount |
| EXPECT_CALL(platform_, DeleteFile(user->ephemeral_mount_path, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, ClearUserKeyring()) |
| .WillRepeatedly(Return(true)); |
| ExpectDownloadsUnmounts(*user); |
| // Detach succeeds. |
| ON_CALL(platform_, DetachLoop(_)).WillByDefault(Return(true)); |
| ASSERT_TRUE(mount_->UnmountCryptohome()); |
| } |
| |
| TEST_P(EphemeralExistingUserSystemTest, OwnerUnknownUnmountNoRemoveTest) { |
| // Checks that when a device is not enterprise enrolled and does not have a |
| // known owner, no stale cryptohomes are removed while unmounting. |
| set_policy(false, "", true); |
| EXPECT_CALL(platform_, ClearUserKeyring()) |
| .WillOnce(Return(true)); |
| ASSERT_TRUE(mount_->UnmountCryptohome()); |
| } |
| |
| TEST_P(EphemeralExistingUserSystemTest, EnterpriseUnmountRemoveTest) { |
| // Checks that when a device is enterprise enrolled, all stale cryptohomes are |
| // removed while unmounting. |
| set_policy(false, "", true); |
| mount_->set_enterprise_owned(true); |
| |
| EXPECT_CALL(platform_, DirectoryExists(_)).WillRepeatedly(Return(true)); |
| |
| std::vector<int> expect_deletion; |
| expect_deletion.push_back(0); |
| expect_deletion.push_back(1); |
| expect_deletion.push_back(2); |
| expect_deletion.push_back(3); |
| PrepareHomedirs(false, &expect_deletion, NULL); |
| |
| // Let Mount know how many vaults there are. |
| EXPECT_CALL(platform_, EnumerateDirectoryEntries(kImageDir, false, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(vaults_), Return(true))); |
| |
| // Don't say any cryptohomes are mounted |
| EXPECT_CALL(platform_, IsDirectoryMounted(_)) |
| .WillRepeatedly(Return(false)); |
| std::vector<FilePath> empty; |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries( |
| AnyOf(FilePath("/home/root/"), FilePath("/home/user/")), |
| _, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(empty), Return(true))); |
| |
| EXPECT_CALL(platform_, ClearUserKeyring()) |
| .WillOnce(Return(true)); |
| |
| ASSERT_TRUE(mount_->UnmountCryptohome()); |
| } |
| |
| TEST_P(EphemeralExistingUserSystemTest, UnmountRemoveTest) { |
| // Checks that when a device is not enterprise enrolled and has a known owner, |
| // all stale cryptohomes are removed while unmounting. |
| TestUser* owner = &helper_.users[3]; |
| set_policy(true, owner->username, true); |
| |
| EXPECT_CALL(platform_, DirectoryExists(_)).WillRepeatedly(Return(true)); |
| |
| // All users but the owner. |
| std::vector<int> expect_deletion; |
| expect_deletion.push_back(0); |
| expect_deletion.push_back(1); |
| expect_deletion.push_back(2); |
| PrepareHomedirs(false, &expect_deletion, NULL); |
| |
| // Let Mount know how many vaults there are. |
| EXPECT_CALL(platform_, EnumerateDirectoryEntries(kImageDir, false, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(vaults_), Return(true))); |
| |
| // Don't say any cryptohomes are mounted |
| EXPECT_CALL(platform_, IsDirectoryMounted(_)) |
| .WillRepeatedly(Return(false)); |
| std::vector<FilePath> empty; |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries( |
| AnyOf(FilePath("/home/root/"), FilePath("/home/user/")), |
| _, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(empty), Return(true))); |
| |
| EXPECT_CALL(platform_, ClearUserKeyring()) |
| .WillOnce(Return(true)); |
| |
| ASSERT_TRUE(mount_->UnmountCryptohome()); |
| } |
| |
| TEST_P(EphemeralExistingUserSystemTest, NonOwnerMountIsEphemeralTest) { |
| // Checks that when a device is not enterprise enrolled and has a known owner, |
| // a mount request for a non-owner user with the |is_ephemeral| flag set |
| // causes a tmpfs cryptohome to be mounted, even if a regular vault exists for |
| // the user. |
| // Since ephemeral users aren't enabled, no vaults will be deleted. |
| TestUser* owner = &helper_.users[3]; |
| set_policy(true, owner->username, false); |
| TestUser* user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| EXPECT_CALL(platform_, DirectoryExists(_)).WillRepeatedly(Return(true)); |
| |
| PrepareHomedirs(true, NULL, NULL); |
| |
| // Let Mount know how many vaults there are. |
| EXPECT_CALL(platform_, EnumerateDirectoryEntries(kImageDir, false, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(vaults_), Return(true))); |
| // Don't say any cryptohomes are mounted |
| EXPECT_CALL(platform_, IsDirectoryMounted(_)) |
| .WillRepeatedly(Return(false)); |
| std::vector<FilePath> empty; |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries( |
| AnyOf(FilePath("/home/root/"), FilePath("/home/user/")), |
| _, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(empty), Return(true))); |
| EXPECT_CALL(platform_, |
| Stat( |
| AnyOf(FilePath("/home/chronos"), |
| MountHelper::GetNewUserPath(user->username)), |
| _)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, |
| Stat( |
| AnyOf(FilePath("/home"), |
| FilePath("/home/root"), |
| brillo::cryptohome::home::GetRootPath(user->username), |
| FilePath("/home/user"), |
| brillo::cryptohome::home::GetUserPath(user->username)), |
| _)) |
| .WillRepeatedly(Return(false)); |
| // Only expect the mounted user to "exist". |
| EXPECT_CALL(platform_, |
| DirectoryExists( |
| Property(&FilePath::value, StartsWith(user->user_mount_path.value())))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, CreateDirectory(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetOwnership(_, _, _, _)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetPermissions(_, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetGroupAccessible(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| FileExists( |
| Property(&FilePath::value, StartsWith("/home/chronos/user")))) |
| .WillRepeatedly(Return(true)); |
| |
| helper_.InjectEphemeralSkeleton(&platform_, |
| FilePath(user->user_ephemeral_mount_path)); |
| |
| EXPECT_CALL(platform_, Stat(user->root_ephemeral_mount_path, _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries(user->ephemeral_mount_path, _, _)) |
| .WillOnce(DoAll(SetArgPointee<2>(empty), Return(true))); |
| |
| EXPECT_CALL(platform_, Unmount(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| ExpectEphemeralCryptohomeMount(*user); |
| |
| // Detach succeeds. |
| ON_CALL(platform_, DetachLoop(_)).WillByDefault(Return(true)); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| ASSERT_TRUE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| } |
| |
| TEST_P(EphemeralExistingUserSystemTest, EnterpriseMountIsEphemeralTest) { |
| // Checks that when a device is enterprise enrolled, a mount request with the |
| // |is_ephemeral| flag set causes a tmpfs cryptohome to be mounted, even |
| // if a regular vault exists for the user. |
| // Since ephemeral users aren't enabled, no vaults will be deleted. |
| set_policy(true, "", false); |
| mount_->set_enterprise_owned(true); |
| |
| TestUser* user = &helper_.users[0]; |
| Credentials credentials(user->username, user->passkey); |
| |
| // Mounting user vault won't be deleted, but tmpfs mount should still be |
| // used. |
| PrepareHomedirs(true, NULL, NULL); |
| |
| // Let Mount know how many vaults there are. |
| EXPECT_CALL(platform_, EnumerateDirectoryEntries(kImageDir, false, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(vaults_), Return(true))); |
| // Don't say any cryptohomes are mounted. |
| EXPECT_CALL(platform_, IsDirectoryMounted(_)) |
| .WillRepeatedly(Return(false)); |
| std::vector<FilePath> empty; |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries( |
| AnyOf(FilePath("/home/root/"), FilePath("/home/user/")), |
| _, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<2>(empty), Return(true))); |
| EXPECT_CALL(platform_, |
| Stat( |
| AnyOf(FilePath("/home/chronos"), |
| MountHelper::GetNewUserPath(user->username)), |
| _)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, |
| Stat( |
| AnyOf(FilePath("/home"), |
| FilePath("/home/root"), |
| brillo::cryptohome::home::GetRootPath(user->username), |
| FilePath("/home/user"), |
| brillo::cryptohome::home::GetUserPath(user->username)), |
| _)) |
| .WillRepeatedly(Return(false)); |
| // Only expect the mounted user to "exist". |
| EXPECT_CALL(platform_, |
| DirectoryExists( |
| Property(&FilePath::value, StartsWith(user->user_mount_path.value())))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, CreateDirectory(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetOwnership(_, _, _, _)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetPermissions(_, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetGroupAccessible(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| FileExists( |
| Property(&FilePath::value, StartsWith("/home/chronos/user")))) |
| .WillRepeatedly(Return(true)); |
| |
| helper_.InjectEphemeralSkeleton(&platform_, |
| FilePath(user->user_ephemeral_mount_path)); |
| |
| EXPECT_CALL(platform_, Stat(user->root_ephemeral_mount_path, _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries(user->ephemeral_mount_path, _, _)) |
| .WillOnce(DoAll(SetArgPointee<2>(empty), Return(true))); |
| |
| EXPECT_CALL(platform_, Unmount(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| ExpectEphemeralCryptohomeMount(*user); |
| |
| // Detach succeeds. |
| ON_CALL(platform_, DetachLoop(_)).WillByDefault(Return(true)); |
| |
| Mount::MountArgs mount_args = GetDefaultMountArgs(); |
| mount_args.create_if_missing = true; |
| mount_args.is_ephemeral = true; |
| MountError error; |
| ASSERT_TRUE(mount_->MountCryptohome(credentials, mount_args, &error)); |
| } |
| |
| TEST_P(EphemeralNoUserSystemTest, MountGuestUserDir) { |
| struct stat fake_root_st; |
| fake_root_st.st_uid = 0; |
| fake_root_st.st_gid = 0; |
| fake_root_st.st_mode = S_IFDIR | S_IRWXU; |
| EXPECT_CALL(platform_, Stat(FilePath("/home"), _)) |
| .Times(3) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(fake_root_st), |
| Return(true))); |
| EXPECT_CALL(platform_, Stat(FilePath("/home/root"), _)) |
| .WillOnce(DoAll(SetArgPointee<1>(fake_root_st), |
| Return(true))); |
| EXPECT_CALL(platform_, |
| Stat(Property(&FilePath::value, StartsWith("/home/root/")), _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, Stat(FilePath("/home/user"), _)) |
| .WillOnce(DoAll(SetArgPointee<1>(fake_root_st), |
| Return(true))); |
| EXPECT_CALL(platform_, |
| Stat(Property(&FilePath::value, StartsWith("/home/user/")), _)) |
| .WillOnce(Return(false)); |
| struct stat fake_user_st; |
| fake_user_st.st_uid = chronos_uid_; |
| fake_user_st.st_gid = chronos_gid_; |
| fake_user_st.st_mode = S_IFDIR | S_IRWXU; |
| EXPECT_CALL(platform_, Stat(FilePath("/home/chronos"), _)) |
| .WillOnce(DoAll(SetArgPointee<1>(fake_user_st), |
| Return(true))); |
| EXPECT_CALL(platform_, CreateDirectory(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetOwnership(_, _, _, _)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetGroupAccessible(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, IsDirectoryMounted(_)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, DirectoryExists(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(_)) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(platform_, StatVFS(FilePath(kEphemeralCryptohomeDir), _)) |
| .WillOnce(Return(true)); |
| const std::string sparse_prefix = |
| FilePath(kEphemeralCryptohomeDir).Append(kSparseFileDir).value(); |
| EXPECT_CALL(platform_, |
| CreateSparseFile( |
| Property(&FilePath::value, StartsWith(sparse_prefix)), _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| AttachLoop(Property(&FilePath::value, StartsWith(sparse_prefix)))) |
| .WillOnce(Return(FilePath("/dev/loop7"))); |
| EXPECT_CALL(platform_, |
| FormatExt4(Property(&FilePath::value, StartsWith(sparse_prefix)), |
| kDefaultExt4FormatOpts, 0)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| Stat(Property(&FilePath::value, StartsWith(kEphemeralCryptohomeDir)), _)) |
| .WillOnce(Return(false)); |
| std::vector<FilePath> empty; |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries( |
| Property(&FilePath::value, StartsWith(kEphemeralCryptohomeDir)), _, _)) |
| .WillOnce(DoAll(SetArgPointee<2>(empty), Return(true))); |
| EXPECT_CALL(platform_, Mount(_, _, _, kDefaultMountFlags, _)) |
| .Times(0); |
| EXPECT_CALL(platform_, |
| Mount(FilePath("/dev/loop7"), _, kEphemeralMountType, |
| kDefaultMountFlags, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| Bind(Property(&FilePath::value, StartsWith(kEphemeralCryptohomeDir)), |
| Property(&FilePath::value, StartsWith("/home/root/")))) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| Bind( |
| Property(&FilePath::value, StartsWith(kEphemeralCryptohomeDir)), |
| Property(&FilePath::value, StartsWith("/home/user/")))) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, Bind(Property(&FilePath::value, |
| StartsWith(kEphemeralCryptohomeDir)), |
| FilePath("/home/chronos/user"))) |
| .WillOnce(Return(true)); |
| EXPECT_CALL( |
| platform_, |
| Bind(Property(&FilePath::value, StartsWith(kEphemeralCryptohomeDir)), |
| Property(&FilePath::value, StartsWith("/home/chronos/u-")))) |
| .WillOnce(Return(true)); |
| // Binding Downloads to MyFiles/Downloads. |
| EXPECT_CALL(platform_, |
| Bind(Property(&FilePath::value, StartsWith("/home/chronos/u-")), |
| Property(&FilePath::value, StartsWith("/home/chronos/u-")))) |
| .WillOnce(Return(true)); |
| EXPECT_CALL( |
| platform_, |
| Bind(Property(&FilePath::value, StartsWith("/home/chronos/user/")), |
| Property(&FilePath::value, StartsWith("/home/chronos/user/")))) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, IsDirectoryMounted( |
| FilePath("/home/chronos/user/MyFiles/Downloads"))) |
| .WillOnce(Return(false)); // first mount |
| EXPECT_CALL(platform_, |
| Bind(Property(&FilePath::value, StartsWith("/home/user/")), |
| Property(&FilePath::value, StartsWith("/home/user/")))) |
| .WillOnce(Return(true)); |
| |
| // Guest -> not an owner. |
| // Also will be called on implicit Unmount. |
| EXPECT_CALL(tpm_, SetUserType(Tpm::UserType::NonOwner)) |
| .WillOnce(Return(true)); |
| |
| ASSERT_TRUE(mount_->MountGuestCryptohome()); |
| |
| // Unmount succeeds. |
| ON_CALL(platform_, Unmount(_, _, _)).WillByDefault(Return(true)); |
| // Detach succeeds. |
| ON_CALL(platform_, DetachLoop(_)).WillByDefault(Return(true)); |
| |
| // Implicit unmount triggers setting user type to non-owner. |
| testing::Mock::VerifyAndClearExpectations(&tpm_); |
| EXPECT_CALL(tpm_, SetUserType(Tpm::UserType::NonOwner)) |
| .WillOnce(Return(true)); |
| } |
| |
| TEST_P(EphemeralNoUserSystemTest, MountGuestUserFailSetUserType) { |
| struct stat fake_root_st; |
| fake_root_st.st_uid = 0; |
| fake_root_st.st_gid = 0; |
| fake_root_st.st_mode = S_IFDIR | S_IRWXU; |
| EXPECT_CALL(platform_, Stat(FilePath("/home"), _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(fake_root_st), |
| Return(true))); |
| EXPECT_CALL(platform_, Stat(FilePath("/home/root"), _)) |
| .WillOnce(DoAll(SetArgPointee<1>(fake_root_st), |
| Return(true))); |
| EXPECT_CALL(platform_, |
| Stat(Property(&FilePath::value, StartsWith("/home/root/")), _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, Stat(FilePath("/home/user"), _)) |
| .WillOnce(DoAll(SetArgPointee<1>(fake_root_st), |
| Return(true))); |
| EXPECT_CALL(platform_, |
| Stat(Property(&FilePath::value, StartsWith("/home/user/")), _)) |
| .WillOnce(Return(false)); |
| struct stat fake_user_st; |
| fake_user_st.st_uid = chronos_uid_; |
| fake_user_st.st_gid = chronos_gid_; |
| fake_user_st.st_mode = S_IFDIR | S_IRWXU; |
| EXPECT_CALL(platform_, Stat(FilePath("/home/chronos"), _)) |
| .WillOnce(DoAll(SetArgPointee<1>(fake_user_st), |
| Return(true))); |
| EXPECT_CALL(platform_, CreateDirectory(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetOwnership(_, _, _, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, SetGroupAccessible(_, _, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, IsDirectoryMounted(_)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, DirectoryExists(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, StatVFS(_, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, CreateSparseFile(_, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, AttachLoop(_)) |
| .WillOnce(Return(FilePath("/dev/loop7"))); |
| EXPECT_CALL(platform_, FormatExt4(_, kDefaultExt4FormatOpts, 0)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| Stat(Property(&FilePath::value, StartsWith(kEphemeralCryptohomeDir)), _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, Mount(_, _, _, kDefaultMountFlags, _)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, Bind(_, _)) |
| .WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(tpm_, SetUserType(Tpm::UserType::NonOwner)) |
| .WillOnce(Return(false)); |
| |
| // Unmount succeeds. |
| ON_CALL(platform_, Unmount(_, _, _)).WillByDefault(Return(true)); |
| // Detach succeeds. |
| ON_CALL(platform_, DetachLoop(_)).WillByDefault(Return(true)); |
| |
| ASSERT_FALSE(mount_->MountGuestCryptohome()); |
| } |
| |
| } // namespace cryptohome |