blob: a4403cefff8d3953614e673db53c7cd851bbcd2d [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.
#include "homedirs.h"
#include <base/stl_util.h>
#include <base/strings/stringprintf.h>
#include <chromeos/constants/cryptohome.h>
#include <chromeos/cryptohome.h>
#include <chromeos/secure_blob.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <policy/mock_device_policy.h>
#include "cryptolib.h"
#include "make_tests.h"
#include "mock_crypto.h"
#include "mock_platform.h"
#include "mock_tpm.h"
#include "mock_user_oldest_activity_timestamp_cache.h"
#include "mock_vault_keyset.h"
#include "mock_vault_keyset_factory.h"
#include "signed_secret.pb.h"
#include "username_passkey.h"
using base::FilePath;
using base::StringPrintf;
using chromeos::SecureBlob;
using std::string;
using ::testing::DoAll;
using ::testing::EndsWith;
using ::testing::HasSubstr;
using ::testing::InSequence;
using ::testing::MatchesRegex;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::SaveArg;
using ::testing::SetArgPointee;
using ::testing::StartsWith;
using ::testing::StrEq;
using ::testing::_;
namespace cryptohome {
extern const char kKeyFile[];
extern const int kKeyFileMax;
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;
}
ACTION_P(SetCleanUpStrategy, clean_up_strategy) {
if (!clean_up_strategy.empty()) {
*arg0 = clean_up_strategy;
return true;
}
return false;
}
namespace {
const char *kTestRoot = "alt_test_home_dir";
struct homedir {
const char *name;
base::Time::Exploded time;
};
const char *kOwner = "<<OWNER>>";
// Note, the order is important. These should be oldest to newest.
const struct homedir kHomedirs[] = {
{ "d5510a8dda6d743c46dadd979a61ae5603529742", { 2011, 1, 6, 1 } },
{ "8f995cdee8f0711fd32e1cf6246424002c483d47", { 2011, 2, 2, 1 } },
{ "973b9640e86f6073c6b6e2759ff3cf3084515e61", { 2011, 3, 2, 1 } },
{ kOwner, { 2011, 4, 5, 1 } }
};
const base::Time::Exploded jan1st2011_exploded = { 2011, 1, 6, 1 };
const base::Time time_jan1 = base::Time::FromUTCExploded(jan1st2011_exploded);
const base::Time::Exploded feb1st2011_exploded = { 2011, 2, 2, 1 };
const base::Time time_feb1 = base::Time::FromUTCExploded(feb1st2011_exploded);
const base::Time::Exploded mar1st2011_exploded = { 2011, 3, 2, 1 };
const base::Time time_mar1 = base::Time::FromUTCExploded(mar1st2011_exploded);
const base::Time::Exploded apr5th2011_exploded = { 2011, 4, 5, 1 };
const base::Time time_apr5 = base::Time::FromUTCExploded(apr5th2011_exploded);
NiceMock<MockFileEnumerator>* CreateMockFileEnumerator() {
return new NiceMock<MockFileEnumerator>;
}
} // namespace
class HomeDirsTest : public ::testing::Test {
public:
HomeDirsTest() { }
virtual ~HomeDirsTest() { }
void SetUp() {
test_helper_.SetUpSystemSalt();
// TODO(wad) Only generate the user data we need. This is time consuming.
test_helper_.InitTestData(kTestRoot, kDefaultUsers, kDefaultUserCount);
crypto_.set_platform(&platform_);
homedirs_.set_shadow_root(kTestRoot);
test_helper_.InjectSystemSalt(&platform_,
StringPrintf("%s/salt", kTestRoot));
set_policy(true, kOwner, false, "");
// Mount() normally sets this.
homedirs_.set_timestamp_cache(&timestamp_cache_);
homedirs_.Init(&platform_, &crypto_);
FilePath fp = FilePath(kTestRoot);
for (unsigned int i = 0; i < arraysize(kHomedirs); i++) {
const struct homedir *hd = &kHomedirs[i];
FilePath path;
std::string owner;
homedirs_.GetOwner(&owner);
path = fp.Append(hd->name);
if (!strcmp(hd->name, kOwner))
path = fp.Append(owner);
homedir_paths_.push_back(path.value());
base::Time t = base::Time::FromUTCExploded(hd->time);
homedir_times_.push_back(t);
}
}
void TearDown() {
test_helper_.TearDownSystemSalt();
}
void set_policy(bool owner_known,
const string& owner,
bool ephemeral_users_enabled,
const string& clean_up_strategy) {
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));
homedirs_.own_policy_provider(new policy::PolicyProvider(device_policy));
}
protected:
HomeDirs homedirs_;
NiceMock<MockCrypto> crypto_;
NiceMock<MockPlatform> platform_;
std::vector<std::string> homedir_paths_;
MakeTests test_helper_;
MockUserOldestActivityTimestampCache timestamp_cache_;
std::vector<base::Time> homedir_times_;
MockVaultKeysetFactory vault_keyset_factory_;
private:
DISALLOW_COPY_AND_ASSIGN(HomeDirsTest);
};
TEST_F(HomeDirsTest, RemoveNonOwnerCryptohomes) {
// Ensure that RemoveNonOwnerCryptohomes does.
EXPECT_CALL(platform_, EnumerateDirectoryEntries(kTestRoot, false, _))
.WillOnce(
DoAll(SetArgPointee<2>(homedir_paths_),
Return(true)));
FilePath user_prefix = chromeos::cryptohome::home::GetUserPathPrefix();
FilePath root_prefix = chromeos::cryptohome::home::GetRootPathPrefix();
EXPECT_CALL(platform_, EnumerateDirectoryEntries(user_prefix.value(), _, _))
.WillOnce(Return(true));
EXPECT_CALL(platform_, EnumerateDirectoryEntries(root_prefix.value(), _, _))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DirectoryExists(_))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform_, IsDirectoryMountedWith(_, _))
.WillRepeatedly(Return(false));
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[0], true))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[1], true))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[2], true))
.WillOnce(Return(true));
homedirs_.RemoveNonOwnerCryptohomes();
}
class FreeDiskSpaceTest : public HomeDirsTest {
public:
FreeDiskSpaceTest() { }
virtual ~FreeDiskSpaceTest() { }
// The first half of HomeDirs::FreeDiskSpace does a purge of the Cache and
// GCached dirs. Unless these are being explicitly tested, we want these to
// always succeed for every test. Set those expectations here.
void ExpectCacheDirCleanupCalls() {
EXPECT_CALL(platform_, EnumerateDirectoryEntries(kTestRoot, false, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(homedir_paths_),
Return(true)));
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
.Times(3).WillRepeatedly(Return(0))
.RetiresOnSaturation();
EXPECT_CALL(platform_, DirectoryExists(_))
.WillRepeatedly(Return(true));
// 4 users * (1 Cache dir + 1 GCache dir) = 8 dir iterations
EXPECT_CALL(platform_, GetFileEnumerator(_, false, _))
.Times(8).WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
}
};
TEST_F(FreeDiskSpaceTest, InitializeTimeCacheWithNoTime) {
// To get to the init logic, we need to fail three kEnoughFreeSpace checks.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
.WillOnce(Return(0))
.WillOnce(Return(0))
.WillOnce(Return(kEnoughFreeSpace - 1));
// Expect cache clean ups.
EXPECT_CALL(platform_, EnumerateDirectoryEntries(kTestRoot, false, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(homedir_paths_),
Return(true)));
EXPECT_CALL(platform_, DirectoryExists(_))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform_, IsDirectoryMountedWith(_, _))
.WillRepeatedly(Return(false));
// The master.* enumerators (wildcard matcher first)
EXPECT_CALL(platform_, GetFileEnumerator(_, false, _))
.Times(4).WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
// Empty enumerators per-user per-cache dirs plus
// enumerators for empty vaults.
EXPECT_CALL(platform_, GetFileEnumerator(HasSubstr("user/Cache"), false, _))
.Times(4).WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
EXPECT_CALL(platform_, GetFileEnumerator(HasSubstr("user/GCache"), false, _))
.Times(4).WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
// Now cover the actual initialization piece
EXPECT_CALL(timestamp_cache_, initialized())
.WillOnce(Return(false));
EXPECT_CALL(timestamp_cache_, Initialize())
.Times(1);
// It then walks the user vault to populate.
MockVaultKeyset* vk[arraysize(kHomedirs)];
EXPECT_CALL(vault_keyset_factory_, New(_, _))
.WillOnce(Return(vk[0] = new MockVaultKeyset()))
.WillOnce(Return(vk[1] = new MockVaultKeyset()))
.WillOnce(Return(vk[2] = new MockVaultKeyset()))
.WillOnce(Return(vk[3] = new MockVaultKeyset()));
for (size_t i = 0; i < arraysize(vk); ++i) {
EXPECT_CALL(*vk[i], Load(_))
.WillRepeatedly(Return(false));
}
homedirs_.set_vault_keyset_factory(&vault_keyset_factory_);
// Expect an addition for all users with no time.
EXPECT_CALL(timestamp_cache_, AddExistingUserNotime(_))
.Times(4);
// Now skip the deletion steps by not having a legit owner.
set_policy(false, "", false, "");
EXPECT_TRUE(homedirs_.FreeDiskSpace());
}
TEST_F(FreeDiskSpaceTest, InitializeTimeCacheWithOneTime) {
// To get to the init logic, we need to fail three kEnoughFreeSpace checks.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
.WillOnce(Return(0))
.WillOnce(Return(0))
.WillOnce(Return(kEnoughFreeSpace - 1));
// Expect cache clean ups.
EXPECT_CALL(platform_, EnumerateDirectoryEntries(kTestRoot, false, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(homedir_paths_),
Return(true)));
EXPECT_CALL(platform_, DirectoryExists(_))
.WillRepeatedly(Return(true));
// The master.* enumerators (wildcard matcher first)
EXPECT_CALL(platform_, GetFileEnumerator(_, false, _))
.Times(3).WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
// Empty enumerators per-user per-cache dirs plus
// enumerators for empty vaults.
EXPECT_CALL(platform_, GetFileEnumerator(HasSubstr("user/Cache"), false, _))
.Times(4).WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
EXPECT_CALL(platform_, GetFileEnumerator(HasSubstr("user/GCache"), false, _))
.Times(4).WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
// Now cover the actual initialization piece
EXPECT_CALL(timestamp_cache_, initialized())
.WillOnce(Return(false));
EXPECT_CALL(timestamp_cache_, Initialize())
.Times(1);
// Skip vault keyset loading to cause "Notime".
EXPECT_CALL(platform_, FileExists(StartsWith(homedir_paths_[0])))
.WillRepeatedly(Return(true));
MockVaultKeyset* vk[arraysize(kHomedirs)];
EXPECT_CALL(vault_keyset_factory_, New(_, _))
.WillOnce(Return(vk[0] = new MockVaultKeyset()))
.WillOnce(Return(vk[1] = new MockVaultKeyset()))
.WillOnce(Return(vk[2] = new MockVaultKeyset()))
.WillOnce(Return(vk[3] = new MockVaultKeyset()));
// The first three will have no time.
size_t i;
for (i = 0; i < arraysize(vk) - 1; ++i) {
EXPECT_CALL(*vk[i], Load(_))
.WillRepeatedly(Return(false));
}
// Owner will have a master.0
NiceMock<MockFileEnumerator> *master0;
EXPECT_CALL(platform_, GetFileEnumerator(homedir_paths_[3], false, _))
.WillOnce(Return(master0 = new NiceMock<MockFileEnumerator>));
EXPECT_CALL(*master0, Next())
.WillOnce(Return(StringPrintf("%s/%s0",
homedir_paths_[3].c_str(), kKeyFile)))
.WillRepeatedly(Return(""));
// The owner will have a time.
EXPECT_CALL(*vk[i], Load(_))
.WillOnce(Return(true));
SerializedVaultKeyset serialized;
serialized.set_last_activity_timestamp(homedir_times_[3].ToInternalValue());
EXPECT_CALL(*vk[i], serialized())
.Times(2)
.WillRepeatedly(ReturnRef(serialized));
homedirs_.set_vault_keyset_factory(&vault_keyset_factory_);
// Expect an addition for all users with no time.
EXPECT_CALL(timestamp_cache_, AddExistingUserNotime(_))
.Times(3);
// Adding the owner
EXPECT_CALL(timestamp_cache_,
AddExistingUser(FilePath(homedir_paths_[3]), homedir_times_[3]))
.Times(1);
// Now skip the deletion steps by not having a legit owner.
set_policy(false, "", false, "");
EXPECT_TRUE(homedirs_.FreeDiskSpace());
}
TEST_F(FreeDiskSpaceTest, NoCacheCleanup) {
// Pretend we have lots of free space
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
.WillOnce(Return(kMinFreeSpaceInBytes + 1));
EXPECT_FALSE(homedirs_.FreeDiskSpace());
}
TEST_F(FreeDiskSpaceTest, OnlyCacheCleanup) {
// Only clean up the Cache data. Not GCache, etc.
EXPECT_CALL(platform_, EnumerateDirectoryEntries(kTestRoot, false, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(homedir_paths_),
Return(true)));
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
.WillOnce(Return(0))
.WillOnce(Return(kEnoughFreeSpace + 1));
EXPECT_CALL(platform_, DirectoryExists(_))
.WillRepeatedly(Return(true));
// Empty enumerators per-user per-cache dirs
NiceMock<MockFileEnumerator>* fe[arraysize(kHomedirs)];
EXPECT_CALL(platform_, GetFileEnumerator(_, false, _))
.WillOnce(Return(fe[0] = new NiceMock<MockFileEnumerator>))
.WillOnce(Return(fe[1] = new NiceMock<MockFileEnumerator>))
.WillOnce(Return(fe[2] = new NiceMock<MockFileEnumerator>))
.WillOnce(Return(fe[3] = new NiceMock<MockFileEnumerator>));
// Exercise the delete file path.
for (size_t f = 0; f < arraysize(fe); ++f) {
EXPECT_CALL(*fe[f], Next())
.WillOnce(Return(StringPrintf("%s/%s", homedir_paths_[f].c_str(),
"Cache/foo")))
.WillRepeatedly(Return(""));
}
EXPECT_CALL(platform_, DeleteFile(EndsWith("/Cache/foo"), true))
.Times(4)
.WillRepeatedly(Return(true));
EXPECT_TRUE(homedirs_.FreeDiskSpace());
}
TEST_F(FreeDiskSpaceTest, CacheAndGCacheCleanup) {
EXPECT_CALL(platform_, EnumerateDirectoryEntries(kTestRoot, false, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(homedir_paths_),
Return(true)));
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
.WillOnce(Return(0))
.WillOnce(Return(0))
.WillOnce(Return(kEnoughFreeSpace + 1));
EXPECT_CALL(platform_, DirectoryExists(_))
.WillRepeatedly(Return(true));
// Empty enumerators per-user per-cache dirs
NiceMock<MockFileEnumerator>* fe[arraysize(kHomedirs)];
EXPECT_CALL(platform_, GetFileEnumerator(EndsWith("/Cache"), false, _))
.WillOnce(Return(fe[0] = new NiceMock<MockFileEnumerator>))
.WillOnce(Return(fe[1] = new NiceMock<MockFileEnumerator>))
.WillOnce(Return(fe[2] = new NiceMock<MockFileEnumerator>))
.WillOnce(Return(fe[3] = new NiceMock<MockFileEnumerator>));
// Exercise the delete file path.
for (size_t f = 0; f < arraysize(fe); ++f) {
EXPECT_CALL(*fe[f], Next())
.WillOnce(Return(StringPrintf("%s/%s", homedir_paths_[f].c_str(),
"Cache/foo")))
.WillRepeatedly(Return(""));
}
EXPECT_CALL(platform_, DeleteFile(EndsWith("/Cache/foo"), true))
.Times(4)
.WillRepeatedly(Return(true));
// Repeat for GCache
EXPECT_CALL(platform_, GetFileEnumerator(EndsWith("GCache/v1/tmp"), false, _))
.WillOnce(Return(fe[0] = new NiceMock<MockFileEnumerator>))
.WillOnce(Return(fe[1] = new NiceMock<MockFileEnumerator>))
.WillOnce(Return(fe[2] = new NiceMock<MockFileEnumerator>))
.WillOnce(Return(fe[3] = new NiceMock<MockFileEnumerator>));
// Exercise the delete file path.
for (size_t f = 0; f < arraysize(fe); ++f) {
EXPECT_CALL(*fe[f], Next())
.WillOnce(Return(StringPrintf("%s/%s", homedir_paths_[f].c_str(),
"GCache/v1/tmp/foo")))
.WillRepeatedly(Return(""));
}
EXPECT_CALL(platform_, DeleteFile(EndsWith("/GCache/v1/tmp/foo"), true))
.Times(4)
.WillRepeatedly(Return(true));
EXPECT_TRUE(homedirs_.FreeDiskSpace());
}
TEST_F(FreeDiskSpaceTest, CleanUpOneUser) {
// Ensure that the oldest user directory deleted, but not any
// others, if the first deletion frees enough space.
EXPECT_CALL(timestamp_cache_, initialized())
.WillRepeatedly(Return(true));
EXPECT_CALL(timestamp_cache_, empty())
.WillOnce(Return(false));
EXPECT_CALL(timestamp_cache_, RemoveOldestUser())
.WillOnce(Return(FilePath(homedir_paths_[0])));
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
.WillOnce(Return(kEnoughFreeSpace + 1));
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[0], true))
.WillOnce(Return(true));
ExpectCacheDirCleanupCalls();
EXPECT_TRUE(homedirs_.FreeDiskSpace());
}
TEST_F(FreeDiskSpaceTest, CleanUpMultipleUsers) {
// Ensure that the two oldest user directories are deleted, but not any
// others, if the second deletion frees enough space.
EXPECT_CALL(timestamp_cache_, initialized())
.WillRepeatedly(Return(true));
EXPECT_CALL(timestamp_cache_, empty())
.WillOnce(Return(false))
.WillOnce(Return(false));
EXPECT_CALL(timestamp_cache_, RemoveOldestUser())
.WillOnce(Return(FilePath(homedir_paths_[0])))
.WillOnce(Return(FilePath(homedir_paths_[1])));
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
.WillOnce(Return(kEnoughFreeSpace - 1))
.WillOnce(Return(kEnoughFreeSpace + 1));
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[0], true))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[1], true))
.WillOnce(Return(true));
ExpectCacheDirCleanupCalls();
EXPECT_TRUE(homedirs_.FreeDiskSpace());
}
TEST_F(FreeDiskSpaceTest, EnterpriseCleanUpAllUsersButLast) {
set_policy(true, "", false, "");
homedirs_.set_enterprise_owned(true);
EXPECT_CALL(timestamp_cache_, initialized())
.WillRepeatedly(Return(true));
EXPECT_CALL(timestamp_cache_, empty())
.WillOnce(Return(false)) // loop
.WillOnce(Return(false)) // enterprise && empty check
.WillOnce(Return(false)) // loop
.WillOnce(Return(true)); // enterprise && empty check
EXPECT_CALL(timestamp_cache_, RemoveOldestUser())
.WillOnce(Return(FilePath(homedir_paths_[0])))
.WillOnce(Return(FilePath(homedir_paths_[1])));
// Confirm deletion isn't time bound; timestamps never checked.
EXPECT_CALL(timestamp_cache_, oldest_known_timestamp()).Times(0);
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
.WillRepeatedly(Return(0));
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[0], true))
.WillOnce(Return(true));
// Most-recent user isn't deleted.
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[1], true)).Times(0);
ExpectCacheDirCleanupCalls();
EXPECT_TRUE(homedirs_.FreeDiskSpace());
}
TEST_F(FreeDiskSpaceTest, CleanUpMultipleNonadjacentUsers) {
// Ensure that the two oldest user directories are deleted, but not any
// others. The owner is inserted in the middle.
EXPECT_CALL(timestamp_cache_, initialized())
.WillRepeatedly(Return(true));
EXPECT_CALL(timestamp_cache_, empty())
.WillOnce(Return(false))
.WillOnce(Return(false))
.WillOnce(Return(false));
EXPECT_CALL(timestamp_cache_, RemoveOldestUser())
.WillOnce(Return(FilePath(homedir_paths_[0])))
.WillOnce(Return(FilePath(homedir_paths_[3])))
.WillOnce(Return(FilePath(homedir_paths_[1])));
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
.WillOnce(Return(0))
// Loop continued before we check disk space for owner.
.WillOnce(Return(kEnoughFreeSpace + 1));
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[0], true))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[1], true))
.WillOnce(Return(true));
// Ensure the owner isn't deleted!
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[3], true))
.Times(0);
ExpectCacheDirCleanupCalls();
EXPECT_TRUE(homedirs_.FreeDiskSpace());
}
TEST_F(FreeDiskSpaceTest, NoOwnerNoEnterpriseNoCleanup) {
// Ensure that no users are deleted with no owner/enterprise-owner.
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
.WillRepeatedly(Return(0));
// Skip re-init
EXPECT_CALL(timestamp_cache_, initialized())
.WillOnce(Return(true));
// No user deletions!
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[0], true))
.Times(0);
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[1], true))
.Times(0);
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[2], true))
.Times(0);
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[3], true))
.Times(0);
// Now skip the deletion steps by not having a legit owner.
set_policy(false, "", false, "");
ExpectCacheDirCleanupCalls();
EXPECT_TRUE(homedirs_.FreeDiskSpace());
}
TEST_F(FreeDiskSpaceTest, ConsumerEphemeralUsers) {
// When ephemeral users are enabled, no cryptohomes are kept except the owner.
set_policy(true, kOwner, true, "");
EXPECT_CALL(platform_, EnumerateDirectoryEntries(kTestRoot, false, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(homedir_paths_),
Return(true)));
EXPECT_CALL(platform_,
EnumerateDirectoryEntries(
chromeos::cryptohome::home::GetUserPathPrefix().value(),
false, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(homedir_paths_),
Return(true)));
EXPECT_CALL(platform_,
EnumerateDirectoryEntries(
chromeos::cryptohome::home::GetRootPathPrefix().value(),
false, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(homedir_paths_),
Return(true)));
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
.WillOnce(Return(kMinFreeSpaceInBytes - 1));
EXPECT_CALL(platform_, DirectoryExists(_))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[0], true))
.WillOnce(Return(true)) // vault
.WillOnce(Return(true)) // user
.WillOnce(Return(true)); // root
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[1], true))
.WillOnce(Return(true))
.WillOnce(Return(true))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[2], true))
.WillOnce(Return(true))
.WillOnce(Return(true))
.WillOnce(Return(true));
// Ensure the owner isn't deleted!
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[3], true))
.Times(0);
EXPECT_TRUE(homedirs_.FreeDiskSpace());
}
TEST_F(FreeDiskSpaceTest, EnterpriseEphemeralUsers) {
// When ephemeral users are enabled, no cryptohomes are kept except the owner.
set_policy(true, "", true, "");
homedirs_.set_enterprise_owned(true);
EXPECT_CALL(platform_, EnumerateDirectoryEntries(kTestRoot, false, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(homedir_paths_),
Return(true)));
EXPECT_CALL(platform_,
EnumerateDirectoryEntries(
chromeos::cryptohome::home::GetUserPathPrefix().value(),
false, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(homedir_paths_),
Return(true)));
EXPECT_CALL(platform_,
EnumerateDirectoryEntries(
chromeos::cryptohome::home::GetRootPathPrefix().value(),
false, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(homedir_paths_),
Return(true)));
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
.WillOnce(Return(kMinFreeSpaceInBytes - 1));
EXPECT_CALL(platform_, DirectoryExists(_))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[0], true))
.WillOnce(Return(true)) // vault
.WillOnce(Return(true)) // user
.WillOnce(Return(true)); // root
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[1], true))
.WillOnce(Return(true))
.WillOnce(Return(true))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[2], true))
.WillOnce(Return(true))
.WillOnce(Return(true))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(homedir_paths_[3], true))
.WillOnce(Return(true))
.WillOnce(Return(true))
.WillOnce(Return(true));
EXPECT_TRUE(homedirs_.FreeDiskSpace());
}
TEST_F(FreeDiskSpaceTest, DontCleanUpMountedUser) {
// Ensure that a user isn't deleted if it appears to be mounted.
EXPECT_CALL(timestamp_cache_, initialized())
.WillRepeatedly(Return(true));
EXPECT_CALL(timestamp_cache_, empty())
.WillOnce(Return(false))
.WillOnce(Return(true));
// This will only be called once (see time below).
EXPECT_CALL(timestamp_cache_, RemoveOldestUser())
.WillOnce(Return(FilePath(homedir_paths_[0])));
EXPECT_CALL(platform_, EnumerateDirectoryEntries(kTestRoot, false, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(homedir_paths_),
Return(true)));
EXPECT_CALL(platform_, AmountOfFreeDiskSpace(kTestRoot))
.WillRepeatedly(Return(0));
EXPECT_CALL(platform_, DirectoryExists(_))
.WillRepeatedly(Return(true));
// Ensure the mounted user never has (G)Cache traversed!
EXPECT_CALL(platform_, GetFileEnumerator(
StartsWith(homedir_paths_[0]), false, _))
.Times(0);
// 3 users * (1 Cache dir + 1 GCache dir) = 6 dir iterations
EXPECT_CALL(platform_, GetFileEnumerator(_, false, _))
.Times(6).WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
EXPECT_CALL(platform_,
IsDirectoryMountedWith(StartsWith(homedir_paths_[0]), _))
.Times(3) // Cache, GCache, user removal
.WillRepeatedly(Return(true));
for (size_t i = 1; i < arraysize(kHomedirs); ++i) {
EXPECT_CALL(platform_,
IsDirectoryMountedWith(StartsWith(homedir_paths_[i]), _))
.Times(2) // Cache, GCache
.WillRepeatedly(Return(false));
}
EXPECT_TRUE(homedirs_.FreeDiskSpace());
}
TEST_F(HomeDirsTest, GoodDecryptTest) {
// create a HomeDirs instance that points to a good shadow root, test that it
// properly authenticates against the first key.
SecureBlob system_salt;
NiceMock<MockTpm> tpm;
homedirs_.crypto()->set_tpm(&tpm);
homedirs_.crypto()->set_use_tpm(false);
ASSERT_TRUE(homedirs_.GetSystemSalt(&system_salt));
set_policy(false, "", false, "");
test_helper_.users[1].InjectKeyset(&platform_);
SecureBlob passkey;
cryptohome::Crypto::PasswordToPasskey(test_helper_.users[1].password,
system_salt, &passkey);
UsernamePasskey up(test_helper_.users[1].username, passkey);
ASSERT_TRUE(homedirs_.AreCredentialsValid(up));
}
TEST_F(HomeDirsTest, BadDecryptTest) {
// create a HomeDirs instance that points to a good shadow root, test that it
// properly denies access with a bad passkey
SecureBlob system_salt;
NiceMock<MockTpm> tpm;
homedirs_.crypto()->set_tpm(&tpm);
homedirs_.crypto()->set_use_tpm(false);
set_policy(false, "", false, "");
test_helper_.users[4].InjectKeyset(&platform_);
SecureBlob passkey;
cryptohome::Crypto::PasswordToPasskey("bogus", system_salt, &passkey);
UsernamePasskey up(test_helper_.users[4].username, passkey);
ASSERT_FALSE(homedirs_.AreCredentialsValid(up));
}
#define MAX_VKS 5
class KeysetManagementTest : public HomeDirsTest {
public:
KeysetManagementTest() { }
virtual ~KeysetManagementTest() { }
void SetUp() {
HomeDirsTest::SetUp();
last_vk_ = -1;
active_vk_ = NULL;
memset(active_vks_, 0, sizeof(active_vks_));
}
void TearDown() {
HomeDirsTest::TearDown();
last_vk_++;
for ( ; last_vk_ < MAX_VKS; ++last_vk_) {
if (active_vks_[last_vk_])
delete active_vks_[last_vk_];
active_vks_[last_vk_] = NULL;
}
last_vk_ = -1;
active_vk_ = NULL;
}
virtual bool VkDecrypt0(const chromeos::SecureBlob& key) {
return memcmp(key.const_data(), keys_[0].const_data(),
key.size()) == 0;
}
virtual const SerializedVaultKeyset& FakeSerialized() const {
return serialized_;
}
virtual SerializedVaultKeyset* FakeMutableSerialized() {
return &serialized_;
}
virtual MockFileEnumerator* NewKeysetFileEnumerator() {
MockFileEnumerator* files = new MockFileEnumerator();
{
InSequence s;
// Single key.
EXPECT_CALL(*files, Next())
.WillOnce(Return(keyset_paths_[0]));
EXPECT_CALL(*files, Next())
.WillOnce(Return(""));
}
return files;
}
virtual MockVaultKeyset* NewActiveVaultKeyset() {
last_vk_++;
CHECK(last_vk_ < MAX_VKS);
active_vk_ = active_vks_[last_vk_];
EXPECT_CALL(*active_vk_, Load(keyset_paths_[0]))
.WillOnce(Return(true));
EXPECT_CALL(*active_vk_, Decrypt(_))
.WillRepeatedly(Invoke(this, &KeysetManagementTest::VkDecrypt0));
EXPECT_CALL(*active_vk_, serialized())
.WillRepeatedly(
Invoke(this, &KeysetManagementTest::FakeSerialized));
EXPECT_CALL(*active_vk_, mutable_serialized())
.WillRepeatedly(
Invoke(this, &KeysetManagementTest::FakeMutableSerialized));
return active_vk_;
}
virtual void KeysetSetUp() {
serialized_.Clear();
NiceMock<MockTpm> tpm;
homedirs_.crypto()->set_tpm(&tpm);
homedirs_.crypto()->set_use_tpm(false);
ASSERT_TRUE(homedirs_.GetSystemSalt(&system_salt_));
set_policy(false, "", false, "");
// Setup the base keyset files for users[1]
keyset_paths_.push_back(test_helper_.users[1].keyset_path);
keys_.push_back(test_helper_.users[1].passkey);
EXPECT_CALL(platform_, GetFileEnumerator(
test_helper_.users[1].base_path, false, _))
.WillRepeatedly(
InvokeWithoutArgs(this,
&KeysetManagementTest::NewKeysetFileEnumerator));
homedirs_.set_vault_keyset_factory(&vault_keyset_factory_);
// Pre-allocate VKs so that each call can advance
// but expectations can be set.
for (int i = 0; i < MAX_VKS; ++i) {
active_vks_[i] = new MockVaultKeyset();
}
active_vk_ = active_vks_[0];
EXPECT_CALL(vault_keyset_factory_, New(_, _))
.WillRepeatedly(
InvokeWithoutArgs(this, &KeysetManagementTest::NewActiveVaultKeyset));
SecureBlob passkey;
cryptohome::Crypto::PasswordToPasskey(test_helper_.users[1].password,
system_salt_, &passkey);
up_.reset(new UsernamePasskey(test_helper_.users[1].username, passkey));
}
int last_vk_;
MockVaultKeyset* active_vk_;
MockVaultKeyset* active_vks_[MAX_VKS];
std::vector<std::string> keyset_paths_;
std::vector<chromeos::SecureBlob> keys_;
scoped_ptr<UsernamePasskey> up_;
SecureBlob system_salt_;
SerializedVaultKeyset serialized_;
};
TEST_F(KeysetManagementTest, AddKeysetSuccess) {
KeysetSetUp();
SecureBlob newkey;
cryptohome::Crypto::PasswordToPasskey("why not", system_salt_, &newkey);
int index = -1;
// The injected keyset in the fixture handles the up_ validation.
EXPECT_CALL(platform_, OpenFile(EndsWith("master.0"), StrEq("wx")))
.WillOnce(Return(reinterpret_cast<FILE*>(NULL)));
EXPECT_CALL(platform_, OpenFile(EndsWith("master.1"), StrEq("wx")))
.WillOnce(Return(reinterpret_cast<FILE*>(0xbeefbeef)));
EXPECT_CALL(*active_vk_, Encrypt(newkey))
.WillOnce(Return(true));
EXPECT_CALL(*active_vk_, Save(EndsWith("master.1")))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(_, _))
.Times(0);
EXPECT_EQ(CRYPTOHOME_ERROR_NOT_SET,
homedirs_.AddKeyset(*up_, newkey, NULL, false, &index));
EXPECT_EQ(index, 1);
}
TEST_F(KeysetManagementTest, AddKeysetClobber) {
KeysetSetUp();
SecureBlob newkey;
cryptohome::Crypto::PasswordToPasskey("why not", system_salt_, &newkey);
serialized_.mutable_key_data()->set_label("current label");
KeyData key_data;
key_data.set_label("current label");
std::string vk_path = "/some/path/master.0";
// Show that 0 is taken.
EXPECT_CALL(platform_, OpenFile(EndsWith("master.0"), StrEq("wx")))
.WillOnce(Return(reinterpret_cast<FILE*>(NULL)));
// Let it claim 1 until it searches the labels.
EXPECT_CALL(platform_, OpenFile(EndsWith("master.1"), StrEq("wx")))
.WillOnce(Return(reinterpret_cast<FILE*>(0xbeefbeef)));
EXPECT_CALL(*active_vk_, Encrypt(newkey))
.WillOnce(Return(true));
EXPECT_CALL(*active_vks_[1], set_legacy_index(_));
EXPECT_CALL(*active_vks_[1], legacy_index())
.WillOnce(Return(0));
EXPECT_CALL(*active_vks_[1], source_file())
.WillOnce(ReturnRef(vk_path));
EXPECT_CALL(*active_vk_, Save(vk_path))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(EndsWith("master.1"), _))
.Times(1);
int index = -1;
EXPECT_EQ(CRYPTOHOME_ERROR_NOT_SET,
homedirs_.AddKeyset(*up_, newkey, &key_data, true, &index));
EXPECT_EQ(index, 0);
}
TEST_F(KeysetManagementTest, AddKeysetNoClobber) {
KeysetSetUp();
SecureBlob newkey;
cryptohome::Crypto::PasswordToPasskey("why not", system_salt_, &newkey);
int index = -1;
serialized_.mutable_key_data()->set_label("current label");
KeyData key_data;
key_data.set_label("current label");
// The injected keyset in the fixture handles the up_ validation.
EXPECT_CALL(platform_, OpenFile(EndsWith("master.0"), StrEq("wx")))
.WillOnce(Return(reinterpret_cast<FILE*>(NULL)));
EXPECT_CALL(platform_, OpenFile(EndsWith("master.1"), StrEq("wx")))
.WillOnce(Return(reinterpret_cast<FILE*>(0xbeefbeef)));
EXPECT_EQ(CRYPTOHOME_ERROR_KEY_LABEL_EXISTS,
homedirs_.AddKeyset(*up_, newkey, &key_data, false, &index));
EXPECT_EQ(index, -1);
}
TEST_F(KeysetManagementTest, UpdateKeysetSuccess) {
KeysetSetUp();
// No need to do PasswordToPasskey as that is the
// external callers job.
SecureBlob new_secret("why not", strlen("why not"));
Key new_key;
new_key.set_secret("why not");
new_key.mutable_data()->set_label("new label");
// The injected keyset in the fixture handles the up_ validation.
serialized_.mutable_key_data()->set_label("current label");
std::string vk_path = "some/path/master.0";
EXPECT_CALL(*active_vk_, source_file())
.WillOnce(ReturnRef(vk_path));
EXPECT_CALL(*active_vk_, Encrypt(new_secret))
.WillOnce(Return(true));
EXPECT_CALL(*active_vk_, Save(vk_path))
.WillOnce(Return(true));
EXPECT_EQ(CRYPTOHOME_ERROR_NOT_SET,
homedirs_.UpdateKeyset(*up_,
const_cast<const Key *>(&new_key),
""));
EXPECT_EQ(serialized_.key_data().label(), new_key.data().label());
}
TEST_F(KeysetManagementTest, UpdateKeysetAuthorizedNoSignature) {
KeysetSetUp();
// No need to do PasswordToPasskey as that is the
// external callers job.
Key new_key;
new_key.set_secret("why not");
new_key.mutable_data()->set_label("new label");
new_key.mutable_data()->set_revision(1);
// The injected keyset in the fixture handles the up_ validation.
KeyData* key_data = serialized_.mutable_key_data();
key_data->set_label("current label");
// Allow the default override on the revision.
key_data->mutable_privileges()->set_update(false);
key_data->mutable_privileges()->set_authorized_update(true);
KeyAuthorizationData* auth_data = key_data->add_authorization_data();
auth_data->set_type(KeyAuthorizationData::KEY_AUTHORIZATION_TYPE_HMACSHA256);
KeyAuthorizationSecret* auth_secret = auth_data->add_secrets();
auth_secret->mutable_usage()->set_sign(true);
const std::string kSomeHMACKey("abc123");
auth_secret->set_symmetric_key(kSomeHMACKey);
EXPECT_EQ(CRYPTOHOME_ERROR_UPDATE_SIGNATURE_INVALID,
homedirs_.UpdateKeyset(*up_,
const_cast<const Key *>(&new_key),
""));
EXPECT_NE(serialized_.key_data().label(), new_key.data().label());
}
TEST_F(KeysetManagementTest, UpdateKeysetAuthorizedSuccess) {
KeysetSetUp();
// No need to do PasswordToPasskey as that is the
// external callers job.
SecureBlob new_pass("why not", strlen("why not"));
Key new_key;
new_key.set_secret("why not");
new_key.mutable_data()->set_label("new label");
// Allow updating over an undefined revision.
new_key.mutable_data()->set_revision(0);
// The injected keyset in the fixture handles the up_ validation.
KeyData* key_data = serialized_.mutable_key_data();
key_data->set_label("current label");
key_data->mutable_privileges()->set_update(false);
key_data->mutable_privileges()->set_authorized_update(true);
KeyAuthorizationData* auth_data = key_data->add_authorization_data();
auth_data->set_type(KeyAuthorizationData::KEY_AUTHORIZATION_TYPE_HMACSHA256);
KeyAuthorizationSecret* auth_secret = auth_data->add_secrets();
auth_secret->mutable_usage()->set_sign(true);
const std::string kSomeHMACKey("abc123");
auth_secret->set_symmetric_key(kSomeHMACKey);
std::string vk_path = "some/path/master.0";
EXPECT_CALL(*active_vk_, source_file())
.WillOnce(ReturnRef(vk_path));
EXPECT_CALL(*active_vk_, Encrypt(new_pass))
.WillOnce(Return(true));
EXPECT_CALL(*active_vk_, Save(vk_path))
.WillOnce(Return(true));
std::string changes_str;
ac::chrome::managedaccounts::account::Secret new_secret;
new_secret.set_revision(new_key.data().revision());
new_secret.set_secret(new_key.secret());
ASSERT_TRUE(new_secret.SerializeToString(&changes_str));
chromeos::SecureBlob hmac_key(auth_secret->symmetric_key());
chromeos::SecureBlob hmac_data(changes_str.c_str(), changes_str.size());
SecureBlob hmac = CryptoLib::HmacSha256(hmac_key, hmac_data);
std::string hmac_str(reinterpret_cast<const char*>(vector_as_array(&hmac)),
hmac.size());
EXPECT_EQ(CRYPTOHOME_ERROR_NOT_SET,
homedirs_.UpdateKeyset(*up_,
const_cast<const Key *>(&new_key),
hmac_str));
EXPECT_EQ(serialized_.key_data().revision(), new_key.data().revision());
}
// Ensure signing matches the test vectors in Chrome.
TEST_F(KeysetManagementTest, UpdateKeysetAuthorizedCompatVector) {
KeysetSetUp();
// The salted password passed in from Chrome.
const char kPassword[] = "OSL3HZZSfK+mDQTYUh3lXhgAzJNWhYz52ax0Bleny7Q=";
// A no-op encryption key.
const char kB64CipherKey[] = "QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUE=\n";
// The signing key pre-installed.
const char kB64SigningKey[] =
"p5TR/34XX0R7IMuffH14BiL1vcdSD8EajPzdIg09z9M=\n";
// The HMAC-256 signature over kPassword using kSigningKey.
const char kB64Signature[] = "KOPQmmJcMr9iMkr36N1cX+G9gDdBBu7zutAxNayPMN4=\n";
// No need to do PasswordToPasskey as that is the
// external callers job.
SecureBlob new_pass(kPassword, sizeof(kPassword)-1);
Key new_key;
new_key.set_secret(std::string(kPassword, sizeof(kPassword)-1));
new_key.mutable_data()->set_label("new label");
// The compat revision to test is '1'.
new_key.mutable_data()->set_revision(1);
// The injected keyset in the fixture handles the up_ validation.
KeyData* key_data = serialized_.mutable_key_data();
key_data->set_label("current label");
key_data->set_revision(0);
key_data->mutable_privileges()->set_update(false);
key_data->mutable_privileges()->set_authorized_update(true);
KeyAuthorizationData* auth_data = key_data->add_authorization_data();
auth_data->set_type(KeyAuthorizationData::KEY_AUTHORIZATION_TYPE_HMACSHA256);
KeyAuthorizationSecret* auth_secret = auth_data->add_secrets();
// Add an encryption secret to ensure later upgrades are viable.
auth_secret->mutable_usage()->set_encrypt(true);
std::string cipher_key = CryptoLib::Base64Decode(kB64CipherKey);
auth_secret->set_symmetric_key(cipher_key);
// Add the signing key
auth_secret = auth_data->add_secrets();
auth_secret->mutable_usage()->set_sign(true);
std::string signing_key = CryptoLib::Base64Decode(kB64SigningKey);
auth_secret->set_symmetric_key(signing_key);
std::string vk_path = "some/path/master.0";
EXPECT_CALL(*active_vk_, source_file())
.WillOnce(ReturnRef(vk_path));
EXPECT_CALL(*active_vk_, Encrypt(new_pass))
.WillOnce(Return(true));
EXPECT_CALL(*active_vk_, Save(vk_path))
.WillOnce(Return(true));
std::string signature = CryptoLib::Base64Decode(kB64Signature);
EXPECT_EQ(CRYPTOHOME_ERROR_NOT_SET,
homedirs_.UpdateKeyset(*up_,
const_cast<const Key *>(&new_key),
signature));
EXPECT_EQ(new_key.data().revision(), serialized_.key_data().revision());
}
TEST_F(KeysetManagementTest, UpdateKeysetAuthorizedNoEqualReplay) {
KeysetSetUp();
// No need to do PasswordToPasskey as that is the
// external callers job.
Key new_key;
new_key.set_secret("why not");
new_key.mutable_data()->set_label("new label");
new_key.mutable_data()->set_revision(100);
// The injected keyset in the fixture handles the up_ validation.
KeyData* key_data = serialized_.mutable_key_data();
key_data->set_revision(100);
key_data->set_label("current label");
key_data->mutable_privileges()->set_update(false);
key_data->mutable_privileges()->set_authorized_update(true);
KeyAuthorizationData* auth_data = key_data->add_authorization_data();
auth_data->set_type(KeyAuthorizationData::KEY_AUTHORIZATION_TYPE_HMACSHA256);
KeyAuthorizationSecret* auth_secret = auth_data->add_secrets();
auth_secret->mutable_usage()->set_sign(true);
const std::string kSomeHMACKey("abc123");
auth_secret->set_symmetric_key(kSomeHMACKey);
std::string changes_str;
ac::chrome::managedaccounts::account::Secret new_secret;
new_secret.set_revision(new_key.data().revision());
new_secret.set_secret(new_key.secret());
ASSERT_TRUE(new_secret.SerializeToString(&changes_str));
chromeos::SecureBlob hmac_key(auth_secret->symmetric_key());
chromeos::SecureBlob hmac_data(changes_str.c_str(), changes_str.size());
SecureBlob hmac = CryptoLib::HmacSha256(hmac_key, hmac_data);
std::string hmac_str(reinterpret_cast<const char*>(vector_as_array(&hmac)),
hmac.size());
EXPECT_EQ(CRYPTOHOME_ERROR_UPDATE_SIGNATURE_INVALID,
homedirs_.UpdateKeyset(*up_,
const_cast<const Key *>(&new_key),
hmac_str));
EXPECT_NE(serialized_.key_data().label(), new_key.data().label());
}
TEST_F(KeysetManagementTest, UpdateKeysetAuthorizedNoLessReplay) {
KeysetSetUp();
// No need to do PasswordToPasskey as that is the
// external callers job.
Key new_key;
new_key.set_secret("why not");
new_key.mutable_data()->set_label("new label");
new_key.mutable_data()->set_revision(0);
// The injected keyset in the fixture handles the up_ validation.
KeyData* key_data = serialized_.mutable_key_data();
key_data->set_revision(1);
key_data->set_label("current label");
key_data->mutable_privileges()->set_update(false);
key_data->mutable_privileges()->set_authorized_update(true);
KeyAuthorizationData* auth_data = key_data->add_authorization_data();
auth_data->set_type(KeyAuthorizationData::KEY_AUTHORIZATION_TYPE_HMACSHA256);
KeyAuthorizationSecret* auth_secret = auth_data->add_secrets();
auth_secret->mutable_usage()->set_sign(true);
const std::string kSomeHMACKey("abc123");
auth_secret->set_symmetric_key(kSomeHMACKey);
std::string changes_str;
ac::chrome::managedaccounts::account::Secret new_secret;
new_secret.set_revision(new_key.data().revision());
new_secret.set_secret(new_key.secret());
ASSERT_TRUE(new_secret.SerializeToString(&changes_str));
chromeos::SecureBlob hmac_key(auth_secret->symmetric_key());
chromeos::SecureBlob hmac_data(changes_str.c_str(), changes_str.size());
SecureBlob hmac = CryptoLib::HmacSha256(hmac_key, hmac_data);
std::string hmac_str(reinterpret_cast<const char*>(vector_as_array(&hmac)),
hmac.size());
EXPECT_EQ(CRYPTOHOME_ERROR_UPDATE_SIGNATURE_INVALID,
homedirs_.UpdateKeyset(*up_,
const_cast<const Key *>(&new_key),
hmac_str));
EXPECT_NE(serialized_.key_data().label(), new_key.data().label());
}
TEST_F(KeysetManagementTest, UpdateKeysetAuthorizedBadSignature) {
KeysetSetUp();
// No need to do PasswordToPasskey as that is the
// external callers job.
Key new_key;
new_key.set_secret("why not");
new_key.mutable_data()->set_label("new label");
new_key.mutable_data()->set_revision(0);
// The injected keyset in the fixture handles the up_ validation.
KeyData* key_data = serialized_.mutable_key_data();
key_data->set_label("current label");
key_data->mutable_privileges()->set_update(false);
key_data->mutable_privileges()->set_authorized_update(true);
KeyAuthorizationData* auth_data = key_data->add_authorization_data();
auth_data->set_type(KeyAuthorizationData::KEY_AUTHORIZATION_TYPE_HMACSHA256);
KeyAuthorizationSecret* auth_secret = auth_data->add_secrets();
auth_secret->mutable_usage()->set_sign(true);
const std::string kSomeHMACKey("abc123");
auth_secret->set_symmetric_key(kSomeHMACKey);
std::string changes_str;
ac::chrome::managedaccounts::account::Secret bad_secret;
bad_secret.set_revision(new_key.data().revision());
bad_secret.set_secret("something else");
ASSERT_TRUE(bad_secret.SerializeToString(&changes_str));
chromeos::SecureBlob hmac_key(auth_secret->symmetric_key());
chromeos::SecureBlob hmac_data(changes_str.c_str(), changes_str.size());
SecureBlob hmac = CryptoLib::HmacSha256(hmac_key, hmac_data);
std::string hmac_str(reinterpret_cast<const char*>(vector_as_array(&hmac)),
hmac.size());
EXPECT_EQ(CRYPTOHOME_ERROR_UPDATE_SIGNATURE_INVALID,
homedirs_.UpdateKeyset(*up_,
const_cast<const Key *>(&new_key),
hmac_str));
EXPECT_NE(serialized_.key_data().label(), new_key.data().label());
}
TEST_F(KeysetManagementTest, UpdateKeysetBadSecret) {
KeysetSetUp();
// No need to do PasswordToPasskey as that is the
// external callers job.
SecureBlob new_secret("why not", strlen("why not"));
Key new_key;
new_key.set_secret("why not");
new_key.mutable_data()->set_label("new label");
// The injected keyset in the fixture handles the up_ validation.
serialized_.mutable_key_data()->set_label("current label");
SecureBlob bad_pass("not it", strlen("not it"));
up_.reset(new UsernamePasskey(test_helper_.users[1].username, bad_pass));
EXPECT_EQ(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED,
homedirs_.UpdateKeyset(*up_,
const_cast<const Key *>(&new_key),
""));
EXPECT_NE(serialized_.key_data().label(), new_key.data().label());
}
TEST_F(KeysetManagementTest, UpdateKeysetNotFoundWithLabel) {
KeysetSetUp();
KeyData some_label;
some_label.set_label("key that doesn't exist");
up_->set_key_data(some_label);
const Key new_key;
EXPECT_EQ(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_NOT_FOUND,
homedirs_.UpdateKeyset(*up_,
&new_key,
""));
}
TEST_F(KeysetManagementTest, RemoveKeysetSuccess) {
KeysetSetUp();
Key remove_key;
remove_key.mutable_data()->set_label("remove me");
// Expect the 0 slot since it'll match all the fake keys.
EXPECT_CALL(*active_vks_[1], set_legacy_index(0));
// Return a different slot to make sure the code is using the right object.
EXPECT_CALL(*active_vks_[1], legacy_index())
.WillOnce(Return(1));
EXPECT_CALL(platform_, FileExists(EndsWith("master.1")))
.WillOnce(Return(true));
serialized_.mutable_key_data()->mutable_privileges()->set_remove(true);
serialized_.mutable_key_data()->set_label("remove me");
EXPECT_EQ(CRYPTOHOME_ERROR_NOT_SET,
homedirs_.RemoveKeyset(*up_, remove_key.data()));
}
TEST_F(KeysetManagementTest, RemoveKeysetNotFound) {
KeysetSetUp();
Key remove_key;
remove_key.mutable_data()->set_label("remove me please");
serialized_.mutable_key_data()->mutable_privileges()->set_remove(true);
serialized_.mutable_key_data()->set_label("the only key in town");
EXPECT_EQ(CRYPTOHOME_ERROR_KEY_NOT_FOUND,
homedirs_.RemoveKeyset(*up_, remove_key.data()));
}
TEST_F(KeysetManagementTest, AddKeysetInvalidCreds) {
KeysetSetUp();
SecureBlob newkey;
cryptohome::Crypto::PasswordToPasskey("why not", system_salt_, &newkey);
int index = -1;
EXPECT_CALL(platform_, DeleteFile(_, _))
.Times(0);
// Try to authenticate with an unknown key.
UsernamePasskey bad_p(test_helper_.users[1].username, newkey);
ASSERT_EQ(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED,
homedirs_.AddKeyset(bad_p, newkey, NULL, false, &index));
EXPECT_EQ(index, -1);
}
TEST_F(KeysetManagementTest, AddKeysetInvalidPrivileges) {
// Check for key use that lacks valid add privileges
KeysetSetUp();
// The injected keyset in the fixture handles the up_ validation.
SecureBlob newkey;
cryptohome::Crypto::PasswordToPasskey("why not", system_salt_, &newkey);
serialized_.mutable_key_data()->mutable_privileges()->set_add(false);
int index = -1;
// Tery to authenticate with a key that cannot add keys.
ASSERT_EQ(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_DENIED,
homedirs_.AddKeyset(*up_, newkey, NULL, false, &index));
EXPECT_EQ(index, -1);
}
TEST_F(KeysetManagementTest, AddKeyset0Available) {
// While this doesn't affect the hole-finding logic, it's good to cover the
// full logical behavior by changing which key auths too.
// master.0 -> master.1
test_helper_.users[1].keyset_path.erase(
test_helper_.users[1].keyset_path.end() - 1);
test_helper_.users[1].keyset_path.append("1");
KeysetSetUp();
// The injected keyset in the fixture handles the up_ validation.
SecureBlob newkey;
cryptohome::Crypto::PasswordToPasskey("why not", system_salt_, &newkey);
EXPECT_CALL(platform_, OpenFile(EndsWith("master.0"), StrEq("wx")))
.WillOnce(Return(reinterpret_cast<FILE*>(0xbeefbeef)));
EXPECT_CALL(*active_vk_, Encrypt(newkey))
.WillOnce(Return(true));
EXPECT_CALL(*active_vk_, Save(EndsWith("master.0")))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(_, _))
.Times(0);
int index = -1;
// Try to authenticate with an unknown key.
ASSERT_EQ(CRYPTOHOME_ERROR_NOT_SET,
homedirs_.AddKeyset(*up_, newkey, NULL, false, &index));
EXPECT_EQ(index, 0);
}
TEST_F(KeysetManagementTest, AddKeyset10Available) {
KeysetSetUp();
// The injected keyset in the fixture handles the up_ validation.
SecureBlob newkey;
cryptohome::Crypto::PasswordToPasskey("why not", system_salt_, &newkey);
EXPECT_CALL(platform_, OpenFile(MatchesRegex(".*/master\\..$"), StrEq("wx")))
.Times(10)
.WillRepeatedly(Return(reinterpret_cast<FILE*>(NULL)));
EXPECT_CALL(platform_, OpenFile(EndsWith("master.10"), StrEq("wx")))
.WillOnce(Return(reinterpret_cast<FILE*>(0xbeefbeef)));
EXPECT_CALL(platform_, DeleteFile(_, _))
.Times(0);
EXPECT_CALL(*active_vk_, Encrypt(newkey))
.WillOnce(Return(true));
EXPECT_CALL(*active_vk_, Save(EndsWith("master.10")))
.WillOnce(Return(true));
int index = -1;
ASSERT_EQ(CRYPTOHOME_ERROR_NOT_SET,
homedirs_.AddKeyset(*up_, newkey, NULL, false, &index));
EXPECT_EQ(index, 10);
}
TEST_F(KeysetManagementTest, AddKeysetNoFreeIndices) {
KeysetSetUp();
// The injected keyset in the fixture handles the up_ validation.
SecureBlob newkey;
cryptohome::Crypto::PasswordToPasskey("why not", system_salt_, &newkey);
EXPECT_CALL(platform_, OpenFile(MatchesRegex(".*/master\\..*$"), StrEq("wx")))
.Times(kKeyFileMax)
.WillRepeatedly(Return(reinterpret_cast<FILE*>(NULL)));
EXPECT_CALL(platform_, DeleteFile(_, _))
.Times(0);
int index = -1;
ASSERT_EQ(CRYPTOHOME_ERROR_KEY_QUOTA_EXCEEDED,
homedirs_.AddKeyset(*up_, newkey, NULL, false, &index));
EXPECT_EQ(index, -1);
}
TEST_F(KeysetManagementTest, AddKeysetEncryptFail) {
KeysetSetUp();
SecureBlob newkey;
cryptohome::Crypto::PasswordToPasskey("why not", system_salt_, &newkey);
int index = -1;
// The injected keyset in the fixture handles the up_ validation.
EXPECT_CALL(platform_, OpenFile(EndsWith("master.0"), StrEq("wx")))
.WillOnce(Return(reinterpret_cast<FILE*>(0xbeefbeef)));
EXPECT_CALL(*active_vk_, Encrypt(newkey))
.WillOnce(Return(false));
EXPECT_CALL(platform_, CloseFile(reinterpret_cast<FILE*>(0xbeefbeef)))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(EndsWith("master.0"), false))
.WillOnce(Return(true));
ASSERT_EQ(CRYPTOHOME_ERROR_BACKING_STORE_FAILURE,
homedirs_.AddKeyset(*up_, newkey, NULL, false, &index));
EXPECT_EQ(index, -1);
}
TEST_F(KeysetManagementTest, AddKeysetSaveFail) {
KeysetSetUp();
SecureBlob newkey;
cryptohome::Crypto::PasswordToPasskey("why not", system_salt_, &newkey);
int index = -1;
// The injected keyset in the fixture handles the up_ validation.
EXPECT_CALL(platform_, OpenFile(EndsWith("master.0"), StrEq("wx")))
.WillOnce(Return(reinterpret_cast<FILE*>(0xbeefbeef)));
EXPECT_CALL(*active_vk_, Encrypt(newkey))
.WillOnce(Return(true));
EXPECT_CALL(*active_vk_, Save(EndsWith("master.0")))
.WillOnce(Return(false));
EXPECT_CALL(platform_, CloseFile(reinterpret_cast<FILE*>(0xbeefbeef)))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(EndsWith("master.0"), false))
.WillOnce(Return(true));
ASSERT_EQ(CRYPTOHOME_ERROR_BACKING_STORE_FAILURE,
homedirs_.AddKeyset(*up_, newkey, NULL, false, &index));
EXPECT_EQ(index, -1);
}
TEST_F(KeysetManagementTest, ForceRemoveKeysetSuccess) {
EXPECT_CALL(platform_, FileExists(EndsWith("master.0")))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(EndsWith("master.0"), false))
.WillOnce(Return(true));
ASSERT_TRUE(homedirs_.ForceRemoveKeyset("a0b0c0", 0));
}
TEST_F(KeysetManagementTest, ForceRemoveKeysetMissingKeyset) {
EXPECT_CALL(platform_, FileExists(EndsWith("master.0")))
.WillOnce(Return(false));
ASSERT_TRUE(homedirs_.ForceRemoveKeyset("a0b0c0", 0));
}
TEST_F(KeysetManagementTest, ForceRemoveKeysetNegativeIndex) {
ASSERT_FALSE(homedirs_.ForceRemoveKeyset("a0b0c0", -1));
}
TEST_F(KeysetManagementTest, ForceRemoveKeysetOverMaxIndex) {
ASSERT_FALSE(homedirs_.ForceRemoveKeyset("a0b0c0", kKeyFileMax));
}
TEST_F(KeysetManagementTest, ForceRemoveKeysetFailedDelete) {
EXPECT_CALL(platform_, FileExists(EndsWith("master.0")))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(EndsWith("master.0"), false))
.WillOnce(Return(false));
ASSERT_FALSE(homedirs_.ForceRemoveKeyset("a0b0c0", 0));
}
TEST_F(KeysetManagementTest, MoveKeysetSuccess_0_to_1) {
const std::string obfuscated = "a0b0c0";
EXPECT_CALL(platform_, FileExists(EndsWith("master.0")))
.WillOnce(Return(true));
EXPECT_CALL(platform_, FileExists(EndsWith("master.1")))
.WillOnce(Return(false));
EXPECT_CALL(platform_, OpenFile(EndsWith("master.1"), StrEq("wx")))
.WillOnce(Return(reinterpret_cast<FILE*>(0xbeefbeef)));
EXPECT_CALL(platform_, Rename(EndsWith("master.0"), EndsWith("master.1")))
.WillOnce(Return(true));
EXPECT_CALL(platform_, CloseFile(reinterpret_cast<FILE*>(0xbeefbeef)))
.WillOnce(Return(true));
ASSERT_TRUE(homedirs_.MoveKeyset(obfuscated, 0, 1));
}
TEST_F(KeysetManagementTest, MoveKeysetSuccess_1_to_99) {
const std::string obfuscated = "a0b0c0";
EXPECT_CALL(platform_, FileExists(EndsWith("master.1")))
.WillOnce(Return(true));
EXPECT_CALL(platform_, FileExists(EndsWith("master.99")))
.WillOnce(Return(false));
EXPECT_CALL(platform_, OpenFile(EndsWith("master.99"), StrEq("wx")))
.WillOnce(Return(reinterpret_cast<FILE*>(0xbeefbeef)));
EXPECT_CALL(platform_, Rename(EndsWith("master.1"), EndsWith("master.99")))
.WillOnce(Return(true));
EXPECT_CALL(platform_, CloseFile(reinterpret_cast<FILE*>(0xbeefbeef)))
.WillOnce(Return(true));
ASSERT_TRUE(homedirs_.MoveKeyset(obfuscated, 1, 99));
}
TEST_F(KeysetManagementTest, MoveKeysetNegativeSource) {
const std::string obfuscated = "a0b0c0";
ASSERT_FALSE(homedirs_.MoveKeyset(obfuscated, -1, 1));
}
TEST_F(KeysetManagementTest, MoveKeysetNegativeDestination) {
const std::string obfuscated = "a0b0c0";
ASSERT_FALSE(homedirs_.MoveKeyset(obfuscated, 1, -1));
}
TEST_F(KeysetManagementTest, MoveKeysetTooLargeDestination) {
const std::string obfuscated = "a0b0c0";
ASSERT_FALSE(homedirs_.MoveKeyset(obfuscated, 1, kKeyFileMax));
}
TEST_F(KeysetManagementTest, MoveKeysetTooLargeSource) {
const std::string obfuscated = "a0b0c0";
ASSERT_FALSE(homedirs_.MoveKeyset(obfuscated, kKeyFileMax, 0));
}
TEST_F(KeysetManagementTest, MoveKeysetMissingSource) {
const std::string obfuscated = "a0b0c0";
EXPECT_CALL(platform_, FileExists(EndsWith("master.0")))
.WillOnce(Return(false));
ASSERT_FALSE(homedirs_.MoveKeyset(obfuscated, 0, 1));
}
TEST_F(KeysetManagementTest, MoveKeysetDestinationExists) {
const std::string obfuscated = "a0b0c0";
EXPECT_CALL(platform_, FileExists(EndsWith("master.0")))
.WillOnce(Return(true));
EXPECT_CALL(platform_, FileExists(EndsWith("master.1")))
.WillOnce(Return(true));
ASSERT_FALSE(homedirs_.MoveKeyset(obfuscated, 0, 1));
}
TEST_F(KeysetManagementTest, MoveKeysetExclusiveOpenFailed) {
const std::string obfuscated = "a0b0c0";
EXPECT_CALL(platform_, FileExists(EndsWith("master.0")))
.WillOnce(Return(true));
EXPECT_CALL(platform_, FileExists(EndsWith("master.1")))
.WillOnce(Return(false));
EXPECT_CALL(platform_, OpenFile(EndsWith("master.1"), StrEq("wx")))
.WillOnce(Return(reinterpret_cast<FILE*>(NULL)));
ASSERT_FALSE(homedirs_.MoveKeyset(obfuscated, 0, 1));
}
TEST_F(KeysetManagementTest, MoveKeysetRenameFailed) {
const std::string obfuscated = "a0b0c0";
EXPECT_CALL(platform_, FileExists(EndsWith("master.0")))
.WillOnce(Return(true));
EXPECT_CALL(platform_, FileExists(EndsWith("master.1")))
.WillOnce(Return(false));
EXPECT_CALL(platform_, OpenFile(EndsWith("master.1"), StrEq("wx")))
.WillOnce(Return(reinterpret_cast<FILE*>(0xbeefbeef)));
EXPECT_CALL(platform_, Rename(EndsWith("master.0"), EndsWith("master.1")))
.WillOnce(Return(false));
EXPECT_CALL(platform_, CloseFile(reinterpret_cast<FILE*>(0xbeefbeef)))
.WillOnce(Return(true));
ASSERT_FALSE(homedirs_.MoveKeyset(obfuscated, 0, 1));
}
} // namespace cryptohome