blob: 813c9a02ca14dcfa62dabbb1371d9c3f2d411920 [file] [log] [blame]
// Copyright (c) 2012 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 "mount.h"
#include <openssl/sha.h>
#include <pwd.h>
#include <string.h> // For memset(), memcpy()
#include <stdlib.h>
#include <sys/types.h>
#include <vector>
#include <base/file_path.h>
#include <base/file_util.h>
#include <base/logging.h>
#include <base/time.h>
#include <chromeos/cryptohome.h>
#include <chromeos/secure_blob.h>
#include <chromeos/utility.h>
#include <gtest/gtest.h>
#include <policy/libpolicy.h>
#include <policy/mock_device_policy.h>
#include "crypto.h"
#include "cryptolib.h"
#include "homedirs.h"
#include "make_tests.h"
#include "mock_platform.h"
#include "mock_tpm.h"
#include "mock_user_session.h"
#include "username_passkey.h"
#include "vault_keyset.h"
#include "vault_keyset.pb.h"
namespace cryptohome {
using chromeos::SecureBlob;
using std::string;
using ::testing::InSequence;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::DoAll;
using ::testing::SetArgumentPointee;
using ::testing::_;
const char kImageDir[] = "test_image_dir";
const char kSkelDir[] = "test_image_dir/skel";
const char kUserDir[] = "user";
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;
}
class MountTest : public ::testing::Test {
public:
MountTest() { }
virtual ~MountTest() { }
void SetUp() {
LoadSystemSalt(kImageDir);
}
void LoadSystemSalt(const char* str_image_path) {
FilePath image_dir(str_image_path);
FilePath path = image_dir.Append("salt");
ASSERT_TRUE(file_util::PathExists(path)) << path.value()
<< " does not exist!";
int64 file_size;
ASSERT_TRUE(file_util::GetFileSize(path, &file_size))
<< "Could not get size of "
<< path.value();
char* buf = new char[file_size];
int data_read = file_util::ReadFile(path, buf, file_size);
system_salt_.assign(buf, buf + data_read);
chromeos::cryptohome::home::SetSystemSaltPath(path.value());
delete[] buf;
}
bool LoadSerializedKeyset(const std::string& key_path,
cryptohome::SerializedVaultKeyset* serialized) {
SecureBlob contents;
Platform platform;
if (!platform.ReadFile(key_path, &contents)) {
return false;
}
return serialized->ParseFromArray(
static_cast<const unsigned char*>(&contents[0]), contents.size());
}
void GetKeysetBlob(const SerializedVaultKeyset& serialized,
SecureBlob* blob) {
SecureBlob local_wrapped_keyset(serialized.wrapped_keyset().length());
serialized.wrapped_keyset().copy(
static_cast<char*>(local_wrapped_keyset.data()),
serialized.wrapped_keyset().length(), 0);
blob->swap(local_wrapped_keyset);
}
void set_policy(Mount* mount,
bool owner_known,
const string& owner,
bool ephemeral_users_enabled) {
policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
EXPECT_CALL(*device_policy, LoadPolicy())
.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(device_policy));
}
protected:
// Protected for trivial access
chromeos::Blob system_salt_;
private:
DISALLOW_COPY_AND_ASSIGN(MountTest);
};
TEST_F(MountTest, BadInitTest) {
// create a Mount instance that points to a bad shadow root
Mount mount;
NiceMock<MockTpm> tpm;
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root("/dev/null");
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
set_policy(&mount, false, "", false);
SecureBlob passkey;
cryptohome::Crypto::PasswordToPasskey(kDefaultUsers[0].password,
system_salt_, &passkey);
UsernamePasskey up(kDefaultUsers[0].username, passkey);
EXPECT_FALSE(mount.Init());
ASSERT_FALSE(mount.AreValid(up));
}
TEST_F(MountTest, CurrentCredentialsTest) {
// create a Mount instance that points to a good shadow root, test that it
// properly authenticates against the first key
Mount mount;
NiceMock<MockTpm> tpm;
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
set_policy(&mount, false, "", false);
SecureBlob passkey;
cryptohome::Crypto::PasswordToPasskey(kDefaultUsers[3].password,
system_salt_, &passkey);
UsernamePasskey up(kDefaultUsers[3].username, passkey);
EXPECT_TRUE(mount.Init());
NiceMock<MockUserSession> user_session;
Crypto crypto;
user_session.Init(SecureBlob());
user_session.SetUser(up);
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(up));
}
TEST_F(MountTest, BadDecryptTest) {
// create a Mount instance that points to a good shadow root, test that it
// properly denies access with a bad passkey
Mount mount;
NiceMock<MockTpm> tpm;
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
set_policy(&mount, false, "", false);
SecureBlob passkey;
cryptohome::Crypto::PasswordToPasskey("bogus", system_salt_, &passkey);
UsernamePasskey up(kDefaultUsers[4].username, passkey);
EXPECT_TRUE(mount.Init());
ASSERT_FALSE(mount.AreValid(up));
}
TEST_F(MountTest, CheckChapsDirectoryCalledWithExistingDir) {
Mount mount;
NiceMock<MockPlatform> platform;
NiceMock<MockTpm> tpm;
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
mount.set_platform(&platform);
EXPECT_TRUE(mount.Init());
EXPECT_CALL(platform, DirectoryExists(_))
.WillRepeatedly(Return(true));
bool permissions_status = false;
EXPECT_TRUE(mount.CheckChapsDirectory(&permissions_status));
EXPECT_TRUE(permissions_status);
}
TEST_F(MountTest, CheckChapsDirectoryCalledWithExistingDirWithBadPerms) {
Mount mount;
NiceMock<MockPlatform> platform;
mode_t bad_perms = S_IXGRP;
NiceMock<MockTpm> tpm;
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
mount.set_platform(&platform);
EXPECT_TRUE(mount.Init());
EXPECT_CALL(platform, DirectoryExists(_))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform, GetPermissions(_, _))
.WillRepeatedly(DoAll(SetArgumentPointee<1>(bad_perms), Return(true)));
bool permissions_status = false;
EXPECT_TRUE(mount.CheckChapsDirectory(&permissions_status));
EXPECT_FALSE(permissions_status);
}
TEST_F(MountTest, CheckChapsDirectoryCalledWithExistingDirWithBadUID) {
Mount mount;
NiceMock<MockPlatform> platform;
NiceMock<MockTpm> tpm;
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
mount.set_platform(&platform);
EXPECT_TRUE(mount.Init());
mode_t bad_uid = 0;
mode_t correct_gid = getgid();
EXPECT_CALL(platform, DirectoryExists(_))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform, GetOwnership(_, _, _))
.WillRepeatedly(DoAll(SetArgumentPointee<1>(bad_uid),
SetArgumentPointee<2>(correct_gid),
Return(true)));
bool permissions_status = false;
EXPECT_TRUE(mount.CheckChapsDirectory(&permissions_status));
EXPECT_FALSE(permissions_status);
}
TEST_F(MountTest, CheckChapsDirectoryCalledWithExistingDirWithBadGID) {
Mount mount;
NiceMock<MockPlatform> platform;
NiceMock<MockTpm> tpm;
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
EXPECT_TRUE(mount.Init());
mode_t correct_uid = getuid();
mode_t bad_gid = 0;
mount.set_platform(&platform);
EXPECT_CALL(platform, DirectoryExists(_))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform, GetOwnership(_, _, _))
.WillRepeatedly(DoAll(SetArgumentPointee<1>(correct_uid),
SetArgumentPointee<2>(bad_gid),
Return(true)));
bool permissions_status = false;
EXPECT_TRUE(mount.CheckChapsDirectory(&permissions_status));
EXPECT_FALSE(permissions_status);
}
TEST_F(MountTest, CheckChapsDirectoryCalledWithNonexistingDirWithFatalError) {
Mount mount;
NiceMock<MockPlatform> platform;
NiceMock<MockTpm> tpm;
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
mount.set_platform(&platform);
EXPECT_TRUE(mount.Init());
EXPECT_CALL(platform, DirectoryExists(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(platform, CreateDirectory(_))
.WillOnce(Return(false))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform, SetOwnership(_, _, _))
.WillOnce(Return(false))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform, SetPermissions(_, _))
.WillRepeatedly(Return(false));
bool permissions_status = false;
EXPECT_FALSE(mount.CheckChapsDirectory(&permissions_status));
EXPECT_FALSE(permissions_status);
EXPECT_FALSE(mount.CheckChapsDirectory(&permissions_status));
EXPECT_FALSE(permissions_status);
EXPECT_FALSE(mount.CheckChapsDirectory(&permissions_status));
EXPECT_FALSE(permissions_status);
}
TEST_F(MountTest, CheckChapsDirectoryCalledWithExistingDirWithFatalError) {
Mount mount;
NiceMock<MockPlatform> platform;
NiceMock<MockTpm> tpm;
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
mount.set_platform(&platform);
EXPECT_TRUE(mount.Init());
EXPECT_CALL(platform, DirectoryExists(_))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform, GetPermissions(_, _))
.WillOnce(Return(false))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform, GetOwnership(_, _, _))
.WillRepeatedly(Return(false));
bool permissions_status = false;
EXPECT_FALSE(mount.CheckChapsDirectory(&permissions_status));
EXPECT_FALSE(permissions_status);
EXPECT_FALSE(mount.CheckChapsDirectory(&permissions_status));
EXPECT_FALSE(permissions_status);
}
TEST_F(MountTest, CreateCryptohomeTest) {
// creates a cryptohome and tests credentials
Mount mount;
HomeDirs homedirs;
NiceMock<MockTpm> tpm;
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
set_policy(&mount, false, "", false);
homedirs.set_shadow_root(kImageDir);
NiceMock<MockPlatform> platform;
mount.set_platform(&platform);
// Test user at index 5 was not created by the test data
SecureBlob passkey;
cryptohome::Crypto::PasswordToPasskey(kDefaultUsers[5].password,
system_salt_, &passkey);
UsernamePasskey up(kDefaultUsers[5].username, passkey);
EXPECT_TRUE(mount.Init());
EXPECT_TRUE(homedirs.Init());
bool created;
ASSERT_TRUE(mount.EnsureCryptohome(up, &created));
ASSERT_TRUE(created);
FilePath image_dir(kImageDir);
FilePath user_path = image_dir.Append(up.GetObfuscatedUsername(system_salt_));
FilePath key_path = user_path.Append("master.0");
FilePath vault_path = user_path.Append(kVaultDir);
ASSERT_TRUE(file_util::PathExists(key_path));
ASSERT_TRUE(file_util::PathExists(vault_path));
ASSERT_FALSE(mount.AreValid(up));
ASSERT_TRUE(homedirs.AreCredentialsValid(up));
}
TEST_F(MountTest, GoodReDecryptTest) {
// create a Mount instance that points to a good shadow root, test that it
// properly re-authenticates against the first key
HomeDirs homedirs;
Mount mount;
NiceMock<MockTpm> tpm;
MockPlatform platform;
Crypto crypto;
mount.get_crypto()->set_tpm(&tpm);
mount.set_use_tpm(false);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
set_policy(&mount, false, "", false);
homedirs.set_shadow_root(kImageDir);
SecureBlob passkey;
cryptohome::Crypto::PasswordToPasskey(kDefaultUsers[6].password,
system_salt_, &passkey);
UsernamePasskey up(kDefaultUsers[6].username, passkey);
EXPECT_TRUE(mount.Init());
EXPECT_TRUE(homedirs.Init());
std::string key_path = mount.GetUserKeyFile(up);
cryptohome::SerializedVaultKeyset serialized;
ASSERT_TRUE(LoadSerializedKeyset(key_path, &serialized));
// Call DecryptVaultKeyset first, allowing migration (the test data is not
// scrypt nor TPM wrapped) to a scrypt-wrapped keyset
VaultKeyset vault_keyset(&platform, &crypto);
MountError error;
ASSERT_TRUE(mount.DecryptVaultKeyset(up, true, &vault_keyset, &serialized,
&error));
// Make sure the keyset is now scrypt wrapped
cryptohome::SerializedVaultKeyset serialized2;
ASSERT_TRUE(LoadSerializedKeyset(key_path, &serialized2));
ASSERT_EQ(SerializedVaultKeyset::SCRYPT_WRAPPED,
(serialized2.flags() &
cryptohome::SerializedVaultKeyset::SCRYPT_WRAPPED));
ASSERT_TRUE(homedirs.AreCredentialsValid(up));
}
TEST_F(MountTest, MountCryptohome) {
// checks that cryptohome tries to mount successfully, and tests that the
// tracked directories are created/replaced as expected
Mount mount;
NiceMock<MockTpm> tpm;
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
set_policy(&mount, false, "", false);
NiceMock<MockPlatform> platform;
mount.set_platform(&platform);
EXPECT_TRUE(mount.Init());
SecureBlob passkey;
cryptohome::Crypto::PasswordToPasskey(kDefaultUsers[10].password,
system_salt_, &passkey);
UsernamePasskey up(kDefaultUsers[10].username, passkey);
EXPECT_CALL(platform, Mount(_, _, _, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform, Bind(_, _))
.WillRepeatedly(Return(true));
MountError error;
ASSERT_TRUE(mount.MountCryptohome(up, Mount::MountArgs(), &error));
FilePath image_dir(kImageDir);
FilePath user_path = image_dir.Append(up.GetObfuscatedUsername(system_salt_));
FilePath vault_path = user_path.Append(kVaultDir);
FilePath vault_user_path = vault_path.Append(kUserDir);
ASSERT_TRUE(file_util::PathExists(vault_user_path.Append(kCacheDir)));
ASSERT_TRUE(file_util::PathExists(vault_user_path.Append(kDownloadsDir)));
ASSERT_TRUE(file_util::PathExists(
vault_user_path.Append(kGCacheDir)
.Append(kGCacheVersionDir).Append(kGCacheTmpDir)));
}
TEST_F(MountTest, MountCryptohomeNoChange) {
// checks that cryptohome doesn't by default re-save the cryptohome when mount
Mount mount;
NiceMock<MockTpm> tpm;
Crypto crypto;
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
set_policy(&mount, false, "", false);
NiceMock<MockPlatform> platform;
mount.set_platform(&platform);
EXPECT_TRUE(mount.Init());
SecureBlob passkey;
cryptohome::Crypto::PasswordToPasskey(kDefaultUsers[11].password,
system_salt_, &passkey);
UsernamePasskey up(kDefaultUsers[11].username, passkey);
VaultKeyset vault_keyset(&platform, &crypto);
SerializedVaultKeyset serialized;
MountError error;
ASSERT_TRUE(mount.DecryptVaultKeyset(up, true, &vault_keyset, &serialized,
&error));
EXPECT_CALL(platform, Mount(_, _, _, _))
.WillOnce(Return(true));
EXPECT_CALL(platform, Bind(_, _))
.Times(3)
.WillRepeatedly(Return(true));
ASSERT_TRUE(mount.MountCryptohome(up, Mount::MountArgs(), &error));
SerializedVaultKeyset new_serialized;
ASSERT_TRUE(mount.DecryptVaultKeyset(up, true, &vault_keyset, &new_serialized,
&error));
SecureBlob lhs;
GetKeysetBlob(serialized, &lhs);
SecureBlob rhs;
GetKeysetBlob(new_serialized, &rhs);
ASSERT_EQ(lhs.size(), rhs.size());
ASSERT_EQ(0, chromeos::SafeMemcmp(lhs.data(), rhs.data(), lhs.size()));
}
TEST_F(MountTest, MountCryptohomeNoCreate) {
// checks that doesn't create the cryptohome for the user on Mount without
// being told to do so.
Mount mount;
NiceMock<MockTpm> tpm;
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
set_policy(&mount, false, "", false);
NiceMock<MockPlatform> platform;
mount.set_platform(&platform);
EXPECT_TRUE(mount.Init());
// Test user at index 12 hasn't been created
SecureBlob passkey;
cryptohome::Crypto::PasswordToPasskey(kDefaultUsers[12].password,
system_salt_, &passkey);
UsernamePasskey up(kDefaultUsers[12].username, passkey);
EXPECT_CALL(platform, Mount(_, _, _, _))
.WillOnce(Return(true));
EXPECT_CALL(platform, Bind(_, _))
.Times(3)
.WillRepeatedly(Return(true));
Mount::MountArgs mount_args;
mount_args.create_if_missing = false;
MountError error;
ASSERT_FALSE(mount.MountCryptohome(up, mount_args, &error));
ASSERT_EQ(MOUNT_ERROR_USER_DOES_NOT_EXIST, error);
FilePath image_dir(kImageDir);
FilePath user_path = image_dir.Append(up.GetObfuscatedUsername(system_salt_));
FilePath vault_path = user_path.Append(kVaultDir);
ASSERT_FALSE(file_util::PathExists(vault_path));
mount_args.create_if_missing = true;
ASSERT_TRUE(mount.MountCryptohome(up, mount_args, &error));
ASSERT_TRUE(file_util::PathExists(vault_path));
FilePath vault_user_path = vault_path.Append(kUserDir);
FilePath subdir_path = vault_user_path.Append(kCacheDir);
ASSERT_TRUE(file_util::PathExists(subdir_path));
}
TEST_F(MountTest, UserActivityTimestampUpdated) {
// checks that user activity timestamp is updated during Mount() and
// periodically while mounted, other Keyset fields remains the same
Mount mount;
NiceMock<MockTpm> tpm;
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
set_policy(&mount, false, "", false);
NiceMock<MockPlatform> platform;
mount.set_platform(&platform);
EXPECT_TRUE(mount.Init());
SecureBlob passkey;
cryptohome::Crypto::PasswordToPasskey(kDefaultUsers[9].password,
system_salt_, &passkey);
UsernamePasskey up(kDefaultUsers[9].username, passkey);
// Mount()
MountError error;
EXPECT_CALL(platform, Mount(_, _, _, _))
.WillOnce(Return(true));
EXPECT_CALL(platform, Bind(_, _))
.Times(3)
.WillRepeatedly(Return(true));
ASSERT_TRUE(mount.MountCryptohome(up, Mount::MountArgs(), &error));
// Update the timestamp. Normally it is called in MountTaskMount::Run() in
// background but here in the test we must call it manually.
static const int kMagicTimestamp = 123;
EXPECT_CALL(platform, GetCurrentTime())
.WillOnce(Return(base::Time::FromInternalValue(kMagicTimestamp)));
mount.UpdateCurrentUserActivityTimestamp(0);
SerializedVaultKeyset serialized1;
ASSERT_TRUE(mount.LoadVaultKeyset(up, &serialized1));
// Check that last activity timestamp is updated.
ASSERT_TRUE(serialized1.has_last_activity_timestamp());
EXPECT_EQ(kMagicTimestamp, serialized1.last_activity_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(_, _, _))
.Times(4)
.WillRepeatedly(Return(true));
mount.UnmountCryptohome();
SerializedVaultKeyset serialized2;
ASSERT_TRUE(mount.LoadVaultKeyset(up, &serialized2));
ASSERT_TRUE(serialized2.has_last_activity_timestamp());
EXPECT_EQ(kMagicTimestamp2, serialized2.last_activity_timestamp());
// Update timestamp again, after user is unmounted. User's activity
// timestamp must not change this.
mount.UpdateCurrentUserActivityTimestamp(0);
SerializedVaultKeyset serialized3;
ASSERT_TRUE(mount.LoadVaultKeyset(up, &serialized3));
ASSERT_TRUE(serialized3.has_last_activity_timestamp());
EXPECT_EQ(serialized3.has_last_activity_timestamp(),
serialized2.has_last_activity_timestamp());
}
// 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 = arraysize(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 = arraysize(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 = arraysize(kAlternateUsers);
// Alternative shadow root directory used for tests adding or removing users.
// This shadow root is recreated before each test.
const char kAltImageDir[] = "alt_test_image_dir";
class AltImageTest : public MountTest {
public:
AltImageTest() { }
void SetUp(const TestUserInfo *users, int user_count) {
// Set up fresh users.
cryptohome::MakeTests make_tests;
make_tests.InitTestData(kAltImageDir, users, user_count);
MountTest::SetUp();
LoadSystemSalt(kAltImageDir);
FilePath root_dir(kAltImageDir);
image_path_.reset(new FilePath[user_count]);
username_passkey_.reset(new UsernamePasskey[user_count]);
for (int user = 0; user != user_count; user++) {
SecureBlob passkey;
cryptohome::Crypto::PasswordToPasskey(users[user].password,
system_salt_, &passkey);
username_passkey_[user].Assign(
UsernamePasskey(users[user].username, passkey));
image_path_[user] = root_dir.Append(
username_passkey_[user].GetObfuscatedUsername(system_salt_));
}
// Initialize Mount object.
mount_.get_crypto()->set_tpm(&tpm_);
mount_.set_shadow_root(kAltImageDir);
mount_.set_use_tpm(false);
set_policy(&mount_, false, "", false);
mount_.set_platform(&platform_);
EXPECT_TRUE(mount_.Init());
}
protected:
Mount mount_;
NiceMock<MockTpm> tpm_;
MockPlatform platform_;
scoped_array<FilePath> image_path_;
scoped_array<UsernamePasskey> username_passkey_;
private:
DISALLOW_COPY_AND_ASSIGN(AltImageTest);
};
// Checks DoAutomaticFreeDiskSpaceControl() to act in different situations when
// free disk space is low.
class DoAutomaticFreeDiskSpaceControlTest : public AltImageTest {
public:
DoAutomaticFreeDiskSpaceControlTest() { }
void SetUp() {
AltImageTest::SetUp(kAlternateUsers, kAlternateUserCount);
}
bool StoreSerializedKeyset(
const string& key_path,
const SerializedVaultKeyset& serialized) {
SecureBlob final_blob(serialized.ByteSize());
serialized.SerializeWithCachedSizesToArray(
static_cast<google::protobuf::uint8*>(final_blob.data()));
unsigned int data_written = file_util::WriteFile(
FilePath(key_path),
static_cast<const char*>(final_blob.const_data()),
final_blob.size());
return data_written == final_blob.size();
}
// Set the user with specified |key_file| old.
bool SetUserTimestamp(const Mount& mount, int user, base::Time timestamp) {
CHECK(user >= 0 && user < kAlternateUserCount);
const string key_file =
mount.GetUserKeyFileForUser(image_path_[user].BaseName().value());
SerializedVaultKeyset serialized;
if (!LoadSerializedKeyset(key_file, &serialized)) {
return false;
}
serialized.set_last_activity_timestamp(timestamp.ToInternalValue());
return StoreSerializedKeyset(key_file, serialized);
}
private:
DISALLOW_COPY_AND_ASSIGN(DoAutomaticFreeDiskSpaceControlTest);
};
TEST_F(DoAutomaticFreeDiskSpaceControlTest, CacheCleanup) {
// Removes caches of all users (except current one, if any).
// For every user, prepare cryptohome contents.
const string contents = "some encrypted contents";
FilePath cache_dir[kAlternateUserCount];
FilePath cache_subdir[kAlternateUserCount];
FilePath gcache_tmp_dir[kAlternateUserCount];
FilePath gcache_tmp_subdir[kAlternateUserCount];
for (int user = 0; user != kAlternateUserCount; user++) {
// Let their Cache dirs be filled with some data.
cache_dir[user] = image_path_[user]
.Append(kVaultDir).Append(kUserHomeSuffix).Append(kCacheDir);
file_util::CreateDirectory(cache_dir[user]);
file_util::WriteFile(cache_dir[user].Append("cached_file"),
contents.c_str(), contents.length());
cache_subdir[user] = cache_dir[user].Append("cache_subdir");
file_util::CreateDirectory(cache_subdir[user]);
file_util::WriteFile(cache_subdir[user].Append("cached_file"),
contents.c_str(), contents.length());
// And the same for GCache dirs.
gcache_tmp_dir[user] = image_path_[user]
.Append(kVaultDir).Append(kUserHomeSuffix).Append(kGCacheDir)
.Append(kGCacheVersionDir).Append(kGCacheTmpDir);
file_util::CreateDirectory(gcache_tmp_dir[user]);
file_util::WriteFile(gcache_tmp_dir[user].Append("gcached_tmp_file"),
contents.c_str(), contents.length());
gcache_tmp_subdir[user] = gcache_tmp_dir[user].Append("gcache_tmp_subdir");
file_util::CreateDirectory(gcache_tmp_subdir[user]);
file_util::WriteFile(gcache_tmp_subdir[user].Append("gcached_tmp_file"),
contents.c_str(), contents.length());
}
// Firstly, pretend we have lots of free space.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillRepeatedly(Return(kMinFreeSpace + 1));
EXPECT_FALSE(mount_.DoAutomaticFreeDiskSpaceControl());
// Check that Cache and GCache are not changed.
for (int user = 0; user != kAlternateUserCount; user++) {
string tested;
EXPECT_TRUE(file_util::PathExists(cache_dir[user]));
EXPECT_TRUE(file_util::ReadFileToString(
cache_dir[user].Append("cached_file"), &tested));
EXPECT_EQ(contents, tested);
EXPECT_TRUE(file_util::PathExists(cache_subdir[user]));
tested.clear();
EXPECT_TRUE(file_util::ReadFileToString(
cache_subdir[user].Append("cached_file"), &tested));
EXPECT_EQ(contents, tested);
tested.clear();
EXPECT_TRUE(file_util::PathExists(gcache_tmp_dir[user]));
EXPECT_TRUE(file_util::ReadFileToString(
gcache_tmp_dir[user].Append("gcached_tmp_file"), &tested));
EXPECT_EQ(contents, tested);
EXPECT_TRUE(file_util::PathExists(gcache_tmp_subdir[user]));
tested.clear();
EXPECT_TRUE(file_util::ReadFileToString(
gcache_tmp_subdir[user].Append("gcached_tmp_file"), &tested));
EXPECT_EQ(contents, tested);
}
// Now pretend we have lack of free space, but only once so only
// Caches should be cleaned.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillOnce(Return(kMinFreeSpace - 1))
.WillRepeatedly(Return(kEnoughFreeSpace));
EXPECT_TRUE(mount_.DoAutomaticFreeDiskSpaceControl());
// Cache must be empty (but not deleted), but GCache must remain.
for (int user = 0; user != kAlternateUserCount; user++) {
EXPECT_TRUE(file_util::IsDirectoryEmpty(cache_dir[user]));
EXPECT_TRUE(file_util::PathExists(cache_dir[user]));
string tested;
EXPECT_TRUE(file_util::PathExists(gcache_tmp_dir[user]));
EXPECT_TRUE(file_util::ReadFileToString(
gcache_tmp_dir[user].Append("gcached_tmp_file"), &tested));
EXPECT_EQ(contents, tested);
EXPECT_TRUE(file_util::PathExists(gcache_tmp_subdir[user]));
tested.clear();
EXPECT_TRUE(file_util::ReadFileToString(
gcache_tmp_subdir[user].Append("gcached_tmp_file"), &tested));
EXPECT_EQ(contents, tested);
}
// Now pretend we have lack of free space.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillRepeatedly(Return(kMinFreeSpace - 1));
EXPECT_TRUE(mount_.DoAutomaticFreeDiskSpaceControl());
// Cache and GCache must be empty (but not deleted).
for (int user = 0; user != kAlternateUserCount; user++) {
EXPECT_TRUE(file_util::IsDirectoryEmpty(cache_dir[user]));
EXPECT_TRUE(file_util::PathExists(cache_dir[user]));
EXPECT_TRUE(file_util::IsDirectoryEmpty(gcache_tmp_dir[user]));
EXPECT_TRUE(file_util::PathExists(gcache_tmp_dir[user]));
// Check that we did not leave any litter.
file_util::Delete(cache_dir[user], true);
file_util::Delete(image_path_[user].Append(kVaultDir)
.Append(kUserHomeSuffix).Append(kGCacheDir), true);
EXPECT_TRUE(file_util::IsDirectoryEmpty(
image_path_[user].Append(kVaultDir).Append(kUserHomeSuffix)));
}
}
TEST_F(DoAutomaticFreeDiskSpaceControlTest, OldUsersCleanupNoTimestamp) {
// Removes old (except owner and the current one, if any) even if
// users had no oldest activity timestamp.
// Setting owner so that old user may be deleted.
set_policy(&mount_, true, "owner@invalid.domain", false);
// Verify that user timestamp cache must be not initialized by now.
UserOldestActivityTimestampCache* user_timestamp =
mount_.user_timestamp_cache();
EXPECT_FALSE(user_timestamp->initialized());
// Now pretend we have lack of free space.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillOnce(Return(kMinFreeSpace - 1))
.WillRepeatedly(Return(kEnoughFreeSpace - 1));
EXPECT_TRUE(mount_.DoAutomaticFreeDiskSpaceControl());
// Make sure no users actually deleted as we didn't put
// user timestamps, all users must remain.
for (int user = 0; user != kAlternateUserCount; user++)
EXPECT_TRUE(file_util::PathExists(image_path_[user]));
// Verify that user timestamp cache must be initialized by now.
EXPECT_TRUE(user_timestamp->initialized());
// Simulate the user[0] have been updated but not old enough.
user_timestamp->UpdateExistingUser(
image_path_[0], base::Time::Now() - kOldUserLastActivityTime / 2);
// Now pretend we have lack of free space.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillOnce(Return(kMinFreeSpace - 1))
.WillRepeatedly(Return(kEnoughFreeSpace - 1));
EXPECT_TRUE(mount_.DoAutomaticFreeDiskSpaceControl());
// Make sure no users actually deleted. Because the only
// timestamp we put is not old enough.
for (int user = 0; user != kAlternateUserCount; user++)
EXPECT_TRUE(file_util::PathExists(image_path_[user]));
// Verify that user timestamp cache must be initialized.
EXPECT_TRUE(user_timestamp->initialized());
// Simulate the user[0] have been updated old enough.
user_timestamp->UpdateExistingUser(
image_path_[0], base::Time::Now() - kOldUserLastActivityTime);
// Now pretend we have lack of free space.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillOnce(Return(kMinFreeSpace - 1))
.WillRepeatedly(Return(kEnoughFreeSpace - 1));
EXPECT_TRUE(mount_.DoAutomaticFreeDiskSpaceControl());
// User[0] is old, user[1,2] have no timestamp => older, user[3] is owner.
EXPECT_FALSE(file_util::PathExists(image_path_[0]));
EXPECT_FALSE(file_util::PathExists(image_path_[1]));
EXPECT_FALSE(file_util::PathExists(image_path_[2]));
EXPECT_TRUE(file_util::PathExists(image_path_[3]));
}
TEST_F(DoAutomaticFreeDiskSpaceControlTest, OldUsersCleanup) {
// Remove old users, oldest first. Stops removing when disk space is enough.
// Setting owner so that old user may be deleted.
set_policy(&mount_, true, "owner@invalid.domain", false);
// Update cached users with following timestamps:
// user[0] is old, user[1] is up to date, user[2] still have no timestamp,
// user[3] is very old, but it is an owner.
SetUserTimestamp(mount_, 0, base::Time::Now() - kOldUserLastActivityTime);
SetUserTimestamp(mount_, 1, base::Time::Now());
SetUserTimestamp(mount_, 3, base::Time::Now() - kOldUserLastActivityTime * 2);
// Now pretend we have lack of free space 3 times.
// So at 1st Caches are deleted, then GCache tmps are deleted
// and then 1 oldest user is deleted.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillOnce(Return(kMinFreeSpace - 1))
.WillOnce(Return(kMinFreeSpace - 1))
.WillOnce(Return(kEnoughFreeSpace - 1))
.WillRepeatedly(Return(kEnoughFreeSpace));
EXPECT_TRUE(mount_.DoAutomaticFreeDiskSpaceControl());
// User[2] should be deleted because we have not updated its
// timestamp (so it does not have one) and 1st user is old, so 2nd
// user is older.
EXPECT_TRUE(file_util::PathExists(image_path_[0]));
EXPECT_TRUE(file_util::PathExists(image_path_[1]));
EXPECT_FALSE(file_util::PathExists(image_path_[2]));
EXPECT_TRUE(file_util::PathExists(image_path_[3]));
// Now pretend we have lack of free space at all times.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillOnce(Return(kMinFreeSpace - 1))
.WillRepeatedly(Return(kEnoughFreeSpace - 1));
EXPECT_TRUE(mount_.DoAutomaticFreeDiskSpaceControl());
// User[0] should be deleted because it is oldest now.
// User[1] should not be deleted because it is up to date.
EXPECT_FALSE(file_util::PathExists(image_path_[0]));
EXPECT_TRUE(file_util::PathExists(image_path_[1]));
EXPECT_FALSE(file_util::PathExists(image_path_[2]));
EXPECT_TRUE(file_util::PathExists(image_path_[3]));
}
TEST_F(DoAutomaticFreeDiskSpaceControlTest, OldUsersCleanupWithRestart) {
// Cryptohomed may restart for some reason and continue nuking users
// as if not restarted. Scenario is same as in test OldUsersCleanup.
// Update cached users with following timestamps:
// user[0] is old, user[1] is up to date, user[2] still have no timestamp,
// user[3] is very old, but it is an owner.
SetUserTimestamp(mount_, 0, base::Time::Now() - kOldUserLastActivityTime);
SetUserTimestamp(mount_, 1, base::Time::Now());
SetUserTimestamp(mount_, 3, base::Time::Now() - kOldUserLastActivityTime * 2);
// Setting owner so that old user may be deleted.
set_policy(&mount_, true, "owner@invalid.domain", false);
// Now pretend we have lack of free space 3 times.
// user[0] is old, user[1] is up to date, user[2] still have no timestamp,
// user[3] is very old, but it is an owner.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillOnce(Return(kMinFreeSpace - 1))
.WillOnce(Return(kMinFreeSpace - 1))
.WillOnce(Return(kEnoughFreeSpace - 1))
.WillRepeatedly(Return(kEnoughFreeSpace));
EXPECT_TRUE(mount_.DoAutomaticFreeDiskSpaceControl());
// User[2] should be deleted because we have not updated its
// timestamp (so it does not have one) and 1st user is old, so 2nd
// user is older.
EXPECT_TRUE(file_util::PathExists(image_path_[0]));
EXPECT_TRUE(file_util::PathExists(image_path_[1]));
EXPECT_FALSE(file_util::PathExists(image_path_[2]));
EXPECT_TRUE(file_util::PathExists(image_path_[3]));
// Forget about mount_ instance as if it has crashed.
// Simulate cryptohome restart. Create new Mount instance.
scoped_ptr<Mount> mount2(new Mount);
mount2->get_crypto()->set_tpm(&tpm_);
mount2->set_shadow_root(kAltImageDir);
mount2->set_use_tpm(false);
mount2->set_platform(&platform_);
EXPECT_TRUE(mount2->Init());
// Setting owner so that old user may be deleted.
set_policy(mount2.get(), true, "owner@invalid.domain", false);
// Now pretend we have lack of free space at all times.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillOnce(Return(kMinFreeSpace - 1))
.WillRepeatedly(Return(kEnoughFreeSpace - 1));
EXPECT_TRUE(mount2->DoAutomaticFreeDiskSpaceControl());
// User[0] should be deleted because it is oldest now.
// User[1] should not be deleted because it is up to date.
EXPECT_FALSE(file_util::PathExists(image_path_[0]));
EXPECT_TRUE(file_util::PathExists(image_path_[1]));
EXPECT_FALSE(file_util::PathExists(image_path_[2]));
EXPECT_TRUE(file_util::PathExists(image_path_[3]));
}
TEST_F(DoAutomaticFreeDiskSpaceControlTest, OldUsersCleanupEphemeral) {
// When ephemeral users are enabled, all users except owner should be removed.
set_policy(&mount_, true, "owner@invalid.domain", true);
// Pretend we have lack of free space.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillOnce(Return(kMinFreeSpace - 1));
EXPECT_TRUE(mount_.DoAutomaticFreeDiskSpaceControl());
// All users except for user[3], who is the owner, should be deleted.
EXPECT_FALSE(file_util::PathExists(image_path_[0]));
EXPECT_FALSE(file_util::PathExists(image_path_[1]));
EXPECT_FALSE(file_util::PathExists(image_path_[2]));
EXPECT_TRUE(file_util::PathExists(image_path_[3]));
}
TEST_F(DoAutomaticFreeDiskSpaceControlTest, OldUsersCleanupNoOwnerSet) {
// No users deleted when no owner known (set) and not in enterprise mode.
// Update cached users with artificial timestamp: user[0] is old,
// Other users still have no timestamp so we consider them even older.
ASSERT_TRUE(SetUserTimestamp(
mount_, 0, base::Time::Now() - kOldUserLastActivityTime));
// Now pretend we have lack of free space at all times - to delete all users.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillOnce(Return(kMinFreeSpace - 1))
.WillRepeatedly(Return(kEnoughFreeSpace - 1));
EXPECT_TRUE(mount_.DoAutomaticFreeDiskSpaceControl());
// All users must remain because, although they are either old or with no
// timestamp, we have not set an owner or enterprise mode.
EXPECT_TRUE(file_util::PathExists(image_path_[0]));
EXPECT_TRUE(file_util::PathExists(image_path_[1]));
EXPECT_TRUE(file_util::PathExists(image_path_[2]));
EXPECT_TRUE(file_util::PathExists(image_path_[3]));
}
TEST_F(DoAutomaticFreeDiskSpaceControlTest, OldUsersCleanupEnterprise) {
// Removes old users in enterprise mode.
// Setting enterprise owned so that all users may be deleted.
set_policy(&mount_, true, "", false);
mount_.set_enterprise_owned(true);
// Update cached users with artificial timestamp: user[0] is old,
// Other users still have no timestamp so we consider them even older.
ASSERT_TRUE(SetUserTimestamp(
mount_, 0, base::Time::Now() - kOldUserLastActivityTime));
// Now pretend we have lack of free space at all times - to delete all users.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillOnce(Return(kMinFreeSpace - 1))
.WillRepeatedly(Return(kEnoughFreeSpace - 1));
EXPECT_TRUE(mount_.DoAutomaticFreeDiskSpaceControl());
// All users must be deleted because they are either old or with no
// timestamp. Owner is not counted because we are in enterprise
// mode.
EXPECT_FALSE(file_util::PathExists(image_path_[0]));
EXPECT_FALSE(file_util::PathExists(image_path_[1]));
EXPECT_FALSE(file_util::PathExists(image_path_[2]));
EXPECT_FALSE(file_util::PathExists(image_path_[3]));
}
TEST_F(DoAutomaticFreeDiskSpaceControlTest, OldUsersCleanupWhenMounted) {
// Do not remove currently mounted user and do remove it when unmounted.
// Setting owner (user[3]) so that old user may be deleted.
set_policy(&mount_, true, "owner@invalid.domain", false);
// Set all users old should one of them have a timestamp.
mount_.set_old_user_last_activity_time(base::TimeDelta::FromMicroseconds(0));
SetUserTimestamp(mount_, 3, base::Time::Now() - kOldUserLastActivityTime);
// Mount() user[0].
MountError error;
EXPECT_CALL(platform_, Mount(_, _, _, _))
.WillOnce(Return(true));
EXPECT_CALL(platform_, Bind(_, _))
.Times(3)
.WillRepeatedly(Return(true));
ASSERT_TRUE(mount_.MountCryptohome(
username_passkey_[0], Mount::MountArgs(), &error));
const string current_uservault = image_path_[0].Append(kVaultDir).value();
LOG(WARNING) << "User[0]: " << current_uservault;
// Update current user timestamp.
// Normally it is done in MountTaskMount::Run() in background.
mount_.UpdateCurrentUserActivityTimestamp(0);
// Now pretend we have lack of free space.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillOnce(Return(kMinFreeSpace - 1))
.WillRepeatedly(Return(kEnoughFreeSpace - 1));
EXPECT_CALL(platform_, IsDirectoryMountedWith(_, _))
.WillRepeatedly(Return(false));
EXPECT_CALL(platform_, IsDirectoryMountedWith(_, current_uservault))
.WillRepeatedly(Return(true));
EXPECT_TRUE(mount_.DoAutomaticFreeDiskSpaceControl());
// User[0] should not be deleted because it is the current,
// user[1,2] should be deleted because they are old.
// user[3] should not be deleted because it is the oener.
EXPECT_TRUE(file_util::PathExists(image_path_[0]));
EXPECT_FALSE(file_util::PathExists(image_path_[1]));
EXPECT_FALSE(file_util::PathExists(image_path_[2]));
EXPECT_TRUE(file_util::PathExists(image_path_[3]));
// Now unmount the user. So it (user[0]) should be cached and may be
// deleted next when it becomes old.
EXPECT_CALL(platform_, Unmount(_, _, _))
.Times(4)
.WillRepeatedly(Return(true));
mount_.UnmountCryptohome();
// Now pretend we have lack of free space.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_))
.WillOnce(Return(kMinFreeSpace - 1))
.WillRepeatedly(Return(kEnoughFreeSpace - 1));
EXPECT_CALL(platform_, IsDirectoryMountedWith(_, _))
.WillRepeatedly(Return(false));
EXPECT_TRUE(mount_.DoAutomaticFreeDiskSpaceControl());
// User[0] should be deleted because it is no more current and we
// delete all users despite their oldness in this test.
EXPECT_FALSE(file_util::PathExists(image_path_[0]));
EXPECT_FALSE(file_util::PathExists(image_path_[1]));
EXPECT_FALSE(file_util::PathExists(image_path_[2]));
EXPECT_TRUE(file_util::PathExists(image_path_[3]));
}
TEST_F(MountTest, MountForUserOrderingTest) {
// Checks that mounts made with MountForUser/BindForUser are undone in the
// right order.
InSequence sequence;
Mount mount;
NiceMock<MockTpm> tpm;
NiceMock<MockPlatform> platform;
mount.set_platform(&platform);
mount.get_crypto()->set_tpm(&tpm);
mount.set_shadow_root(kImageDir);
mount.set_skel_source(kSkelDir);
mount.set_use_tpm(false);
EXPECT_TRUE(mount.Init());
UserSession session;
Crypto crypto;
SecureBlob salt;
salt.resize(16);
CryptoLib::GetSecureRandom(static_cast<unsigned char*>(salt.data()),
salt.size());
session.Init(salt);
UsernamePasskey up("username", SecureBlob("password", 8));
EXPECT_TRUE(session.SetUser(up));
std::string src = "/src";
std::string dest0 = "/dest/foo";
std::string dest1 = "/dest/bar";
std::string dest2 = "/dest/baz";
EXPECT_CALL(platform, Mount(src, dest0, _, _))
.WillOnce(Return(true));
EXPECT_CALL(platform, Bind(src, dest1))
.WillOnce(Return(true));
EXPECT_CALL(platform, Mount(src, dest2, _, _))
.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(mount.MountForUser(&session, src, dest0, "", ""));
EXPECT_TRUE(mount.BindForUser(&session, src, dest1));
EXPECT_TRUE(mount.MountForUser(&session, src, dest2, "", ""));
mount.UnmountAllForUser(&session);
EXPECT_FALSE(mount.UnmountForUser(&session));
}
class EphemeralTest : public AltImageTest {
public:
EphemeralTest() { }
void SetUp(const TestUserInfo *users, int user_count) {
AltImageTest::SetUp(users, user_count);
user_path_.reset(new FilePath[user_count]);
root_path_.reset(new FilePath[user_count]);
for (int user = 0; user != user_count; user++) {
const string username = users[user].username;
user_path_[user] = chromeos::cryptohome::home::GetUserPath(username);
root_path_[user] = chromeos::cryptohome::home::GetRootPath(username);
if (users[user].create)
mount_.EnsureUserMountPoints(username_passkey_[user]);
}
}
protected:
scoped_array<FilePath> user_path_;
scoped_array<FilePath> root_path_;
private:
DISALLOW_COPY_AND_ASSIGN(EphemeralTest);
};
class EphemeralNoUserSystemTest : public EphemeralTest {
public:
EphemeralNoUserSystemTest() { }
void SetUp() {
EphemeralTest::SetUp(kNoUsers, kNoUserCount);
}
private:
DISALLOW_COPY_AND_ASSIGN(EphemeralNoUserSystemTest);
};
TEST_F(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(&mount_, false, "", true);
EXPECT_CALL(platform_, Mount(_, _, _, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform_, Mount(_, _, kEphemeralMountType, _))
.Times(0);
EXPECT_CALL(platform_, Bind(_, _))
.WillRepeatedly(Return(true));
Mount::MountArgs mount_args;
mount_args.create_if_missing = true;
MountError error;
ASSERT_TRUE(mount_.MountCryptohome(username_passkey_[0],
mount_args,
&error));
EXPECT_TRUE(file_util::PathExists(user_path_[0]));
EXPECT_TRUE(file_util::PathExists(root_path_[0]));
EXPECT_TRUE(file_util::PathExists(image_path_[0]));
}
TEST_F(EphemeralNoUserSystemTest, EnterpriseMountNoCreateTest) {
// Checks that when a device is enterprise enrolled, a tmpfs cryptohome is
// mounted and no regular vault is created.
set_policy(&mount_, false, "", true);
mount_.set_enterprise_owned(true);
EXPECT_CALL(platform_, Mount(_, _, _, _))
.Times(0);
EXPECT_CALL(platform_, Mount(_, _, kEphemeralMountType, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform_, Bind(_, _))
.WillRepeatedly(Return(true));
Mount::MountArgs mount_args;
mount_args.create_if_missing = true;
MountError error;
ASSERT_TRUE(mount_.MountCryptohome(username_passkey_[0],
mount_args,
&error));
EXPECT_TRUE(file_util::PathExists(user_path_[0]));
EXPECT_TRUE(file_util::PathExists(root_path_[0]));
EXPECT_FALSE(file_util::PathExists(image_path_[0]));
}
class EphemeralOwnerOnlySystemTest : public EphemeralTest {
public:
EphemeralOwnerOnlySystemTest() { }
void SetUp() {
EphemeralTest::SetUp(kOwnerOnlyUsers, kOwnerOnlyUserCount);
}
private:
DISALLOW_COPY_AND_ASSIGN(EphemeralOwnerOnlySystemTest);
};
TEST_F(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.
set_policy(&mount_, true, "owner@invalid.domain", true);
EXPECT_CALL(platform_, Mount(_, _, _, _))
.Times(0);
EXPECT_CALL(platform_, Mount(_, _, kEphemeralMountType, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform_, Bind(_, _))
.WillRepeatedly(Return(true));
Mount::MountArgs mount_args;
mount_args.create_if_missing = true;
MountError error;
ASSERT_TRUE(mount_.MountCryptohome(username_passkey_[0],
mount_args,
&error));
EXPECT_TRUE(file_util::PathExists(user_path_[0]));
EXPECT_TRUE(file_util::PathExists(root_path_[0]));
EXPECT_FALSE(file_util::PathExists(image_path_[0]));
}
class EphemeralExistingUserSystemTest : public EphemeralTest {
public:
EphemeralExistingUserSystemTest() { }
void SetUp() {
EphemeralTest::SetUp(kAlternateUsers, kAlternateUserCount);
}
private:
DISALLOW_COPY_AND_ASSIGN(EphemeralExistingUserSystemTest);
};
TEST_F(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(&mount_, false, "", true);
EXPECT_CALL(platform_, Mount(_, _, _, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform_, Mount(_, _, kEphemeralMountType, _))
.Times(0);
EXPECT_CALL(platform_, Bind(_, _))
.WillRepeatedly(Return(true));
Mount::MountArgs mount_args;
mount_args.create_if_missing = true;
MountError error;
ASSERT_TRUE(mount_.MountCryptohome(username_passkey_[0],
mount_args,
&error));
for (int user = 0; user != kAlternateUserCount; user++) {
EXPECT_TRUE(file_util::PathExists(user_path_[user]));
EXPECT_TRUE(file_util::PathExists(root_path_[user]));
EXPECT_TRUE(file_util::PathExists(image_path_[user]));
}
}
TEST_F(EphemeralExistingUserSystemTest, EnterpriseMountRemoveTest) {
// Checks that when a device is enterprise enrolled, all stale cryptohomes are
// removed while mounting.
set_policy(&mount_, false, "", true);
mount_.set_enterprise_owned(true);
EXPECT_CALL(platform_, Mount(_, _, _, _))
.Times(0);
EXPECT_CALL(platform_, Mount(_, _, kEphemeralMountType, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform_, Bind(_, _))
.WillRepeatedly(Return(true));
Mount::MountArgs mount_args;
mount_args.create_if_missing = true;
MountError error;
ASSERT_TRUE(mount_.MountCryptohome(username_passkey_[0],
mount_args,
&error));
EXPECT_TRUE(file_util::PathExists(user_path_[0]));
EXPECT_TRUE(file_util::PathExists(root_path_[0]));
EXPECT_FALSE(file_util::PathExists(image_path_[0]));
for (int user = 1; user != kAlternateUserCount; user++) {
EXPECT_FALSE(file_util::PathExists(user_path_[user]));
EXPECT_FALSE(file_util::PathExists(root_path_[user]));
EXPECT_FALSE(file_util::PathExists(image_path_[user]));
}
}
TEST_F(EphemeralExistingUserSystemTest, MountRemoveTest) {
// Checks that when a device is not enterprise enrolled and has a known owner,
// all stale cryptohomes are removed while mounting.
set_policy(&mount_, true, "owner@invalid.domain", true);
EXPECT_CALL(platform_, Mount(_, _, _, _))
.Times(0);
EXPECT_CALL(platform_, Mount(_, _, kEphemeralMountType, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform_, Bind(_, _))
.WillRepeatedly(Return(true));
Mount::MountArgs mount_args;
mount_args.create_if_missing = true;
MountError error;
ASSERT_TRUE(mount_.MountCryptohome(username_passkey_[0],
mount_args,
&error));
EXPECT_TRUE(file_util::PathExists(user_path_[0]));
EXPECT_TRUE(file_util::PathExists(root_path_[0]));
EXPECT_FALSE(file_util::PathExists(image_path_[0]));
for (int user = 1; user != kAlternateUserCount; user++) {
if (username_passkey_[user].username() ==
"owner@invalid.domain") {
// The owner's cryptohome and mount points should have been preserved.
EXPECT_TRUE(file_util::PathExists(user_path_[user]));
EXPECT_TRUE(file_util::PathExists(root_path_[user]));
EXPECT_TRUE(file_util::PathExists(image_path_[user]));
} else {
EXPECT_FALSE(file_util::PathExists(user_path_[user]));
EXPECT_FALSE(file_util::PathExists(root_path_[user]));
EXPECT_FALSE(file_util::PathExists(image_path_[user]));
}
}
}
TEST_F(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(&mount_, false, "", true);
ASSERT_TRUE(mount_.UnmountCryptohome());
for (int user = 0; user != kAlternateUserCount; user++) {
EXPECT_TRUE(file_util::PathExists(user_path_[user]));
EXPECT_TRUE(file_util::PathExists(root_path_[user]));
EXPECT_TRUE(file_util::PathExists(image_path_[user]));
}
}
TEST_F(EphemeralExistingUserSystemTest, EnterpriseUnmountRemoveTest) {
// Checks that when a device is enterprise enrolled, all stale cryptohomes are
// removed while unmounting.
set_policy(&mount_, false, "", true);
mount_.set_enterprise_owned(true);
ASSERT_TRUE(mount_.UnmountCryptohome());
for (int user = 0; user != kAlternateUserCount; user++) {
EXPECT_FALSE(file_util::PathExists(user_path_[user]));
EXPECT_FALSE(file_util::PathExists(root_path_[user]));
EXPECT_FALSE(file_util::PathExists(image_path_[user]));
}
}
TEST_F(EphemeralExistingUserSystemTest, UnmountRemoveTest) {
// Checks that when a device is not enterprise enrolled and has a known owner,
// all stale cryptohomes are removed while unmounting.
set_policy(&mount_, true, "owner@invalid.domain", true);
ASSERT_TRUE(mount_.UnmountCryptohome());
for (int user = 0; user != kAlternateUserCount; user++) {
if (username_passkey_[user].username() == "owner@invalid.domain") {
// The owner's cryptohome and mount points should have been preserved.
EXPECT_TRUE(file_util::PathExists(user_path_[user]));
EXPECT_TRUE(file_util::PathExists(root_path_[user]));
EXPECT_TRUE(file_util::PathExists(image_path_[user]));
} else {
EXPECT_FALSE(file_util::PathExists(user_path_[user]));
EXPECT_FALSE(file_util::PathExists(root_path_[user]));
EXPECT_FALSE(file_util::PathExists(image_path_[user]));
}
}
}
} // namespace cryptohome