blob: bce3a2ed9902e591d68e91c608eb99552a273eb6 [file] [log] [blame]
// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Creates credential stores for testing
#include "cryptohome/make_tests.h"
#include <openssl/evp.h>
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <base/bind.h>
#include <base/bind_helpers.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/memory/ref_counted.h>
#include <base/stl_util.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <brillo/cryptohome.h>
#include <brillo/secure_blob.h>
#include <policy/libpolicy.h>
#include <policy/mock_device_policy.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "cryptohome/crypto.h"
#include "cryptohome/cryptolib.h"
#include "cryptohome/mock_crypto.h"
#include "cryptohome/mock_platform.h"
#include "cryptohome/mock_tpm.h"
#include "cryptohome/mount.h"
#include "cryptohome/mount_helper.h"
#include "cryptohome/user_oldest_activity_timestamp_cache.h"
#include "cryptohome/vault_keyset.h"
using base::FilePath;
using brillo::SecureBlob;
using ::testing::_;
using ::testing::AnyOf;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Property;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::SetArgPointee;
using ::testing::StartsWith;
namespace cryptohome {
// struct TestUserInfo {
// const char* username;
// const char* password;
// bool create;
// };
const TestUserInfo kDefaultUsers[] = {
{"testuser0@invalid.domain", "zero", true, false},
{"testuser1@invalid.domain", "one", true, false},
{"testuser2@invalid.domain", "two", true, false},
{"testuser3@invalid.domain", "three", true, false},
{"testuser4@invalid.domain", "four", true, false},
{"testuser5@invalid.domain", "five", false, false},
{"testuser6@invalid.domain", "six", true, false},
{"testuser7@invalid.domain", "seven", true, false},
{"testuser8@invalid.domain", "eight", true, false},
{"testuser9@invalid.domain", "nine", true, false},
{"testuser10@invalid.domain", "ten", true, false},
{"testuser11@invalid.domain", "eleven", true, false},
{"testuser12@invalid.domain", "twelve", false, false},
{"testuser13@invalid.domain", "thirteen", true, false},
{"testuser14@invalid.domain", "0014", true, true},
};
const size_t kDefaultUserCount = base::size(kDefaultUsers);
MakeTests::MakeTests() {}
void MakeTests::InitTestData(const FilePath& image_dir,
const TestUserInfo* test_users,
size_t test_user_count,
bool force_ecryptfs) {
CHECK(system_salt.size()) << "Call SetUpSystemSalt() first";
users.clear();
users.resize(test_user_count);
const TestUserInfo* user_info = test_users;
for (size_t id = 0; id < test_user_count; ++id, ++user_info) {
TestUser* user = &users[id];
user->FromInfo(user_info, image_dir);
user->GenerateCredentials(force_ecryptfs);
}
}
void MakeTests::SetUpSystemSalt() {
std::string* salt = new std::string(CRYPTOHOME_DEFAULT_SALT_LENGTH, 'A');
system_salt.resize(salt->size());
memcpy(&system_salt[0], salt->data(), salt->size());
brillo::cryptohome::home::SetSystemSalt(salt);
}
void MakeTests::TearDownSystemSalt() {
std::string* salt = brillo::cryptohome::home::GetSystemSalt();
brillo::cryptohome::home::SetSystemSalt(NULL);
delete salt;
}
void MakeTests::InjectSystemSalt(MockPlatform* platform, const FilePath& path) {
CHECK(brillo::cryptohome::home::GetSystemSalt());
EXPECT_CALL(*platform, FileExists(path)).WillRepeatedly(Return(true));
EXPECT_CALL(*platform, GetFileSize(path, _))
.WillRepeatedly(
DoAll(SetArgPointee<1>(system_salt.size()), Return(true)));
EXPECT_CALL(*platform, ReadFileToSecureBlob(path, _))
.WillRepeatedly(DoAll(SetArgPointee<1>(system_salt), Return(true)));
}
void MakeTests::InjectEphemeralSkeleton(MockPlatform* platform,
const FilePath& root) {
EXPECT_CALL(*platform,
SetOwnership(Property(&FilePath::value, StartsWith(root.value())),
_, _, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(*platform, DirectoryExists(Property(&FilePath::value,
StartsWith(root.value()))))
.WillRepeatedly(Return(false));
EXPECT_CALL(*platform,
FileExists(Property(&FilePath::value, StartsWith(root.value()))))
.WillRepeatedly(Return(false));
EXPECT_CALL(*platform,
SetGroupAccessible(
Property(&FilePath::value, StartsWith(root.value())), _, _))
.WillRepeatedly(Return(true));
}
void TestUser::FromInfo(const struct TestUserInfo* info,
const FilePath& image_dir) {
username = info->username;
password = info->password;
create = info->create;
is_le_credential = info->is_le_credential;
use_key_data = is_le_credential ? true : false;
// Stub system salt must already be in place. See MountTest::SetUp().
// Sanitized usernames and obfuscated ones differ by case. Accomodate both.
// TODO(ellyjones) fix this discrepancy!
sanitized_username = brillo::cryptohome::home::SanitizeUserName(username);
obfuscated_username = sanitized_username;
std::transform(obfuscated_username.begin(), obfuscated_username.end(),
obfuscated_username.begin(), ::tolower);
// Both pass this check though.
DCHECK(brillo::cryptohome::home::IsSanitizedUserName(obfuscated_username));
shadow_root = image_dir;
skel_dir = image_dir.Append("skel");
base_path = image_dir.Append(obfuscated_username);
image_path = base_path.Append("image");
vault_path = base_path.Append("vault");
vault_mount_path = base_path.Append("mount");
ephemeral_mount_path = FilePath(kEphemeralCryptohomeDir)
.Append("ephemeral_mount")
.Append(obfuscated_username);
tracked_directories_json_path = base_path.Append("tracked_directories.json");
root_vault_path = vault_path.Append("root");
user_vault_path = vault_path.Append("user");
root_vault_mount_path = vault_mount_path.Append("root");
user_vault_mount_path = vault_mount_path.Append("user");
root_ephemeral_mount_path = ephemeral_mount_path.Append("root");
user_ephemeral_mount_path = ephemeral_mount_path.Append("user");
keyset_path = base_path.Append("master.0");
timestamp_path = base_path.Append("master.0.timestamp");
mount_prefix = brillo::cryptohome::home::GetUserPathPrefix().DirName();
legacy_user_mount_path = FilePath("/home/chronos/user");
user_mount_path =
brillo::cryptohome::home::GetUserPath(username).StripTrailingSeparators();
user_mount_prefix =
brillo::cryptohome::home::GetUserPathPrefix().StripTrailingSeparators();
root_mount_path =
brillo::cryptohome::home::GetRootPath(username).StripTrailingSeparators();
root_mount_prefix =
brillo::cryptohome::home::GetRootPathPrefix().StripTrailingSeparators();
}
void TestUser::GenerateCredentials(bool force_ecryptfs) {
std::string* system_salt = brillo::cryptohome::home::GetSystemSalt();
brillo::Blob salt(system_salt->begin(), system_salt->end());
SecureBlob sec_salt(*system_salt);
NiceMock<MockTpm> tpm;
NiceMock<MockPlatform> platform;
Crypto crypto(&platform);
crypto.set_use_tpm(false);
crypto.set_disable_logging_for_testing(/*disable=*/true);
CryptoLib::SetScryptTestingParams();
UserOldestActivityTimestampCache timestamp_cache;
scoped_refptr<Mount> mount = new Mount();
mount->set_shadow_root(shadow_root);
mount->set_skel_source(skel_dir);
mount->set_use_tpm(false);
NiceMock<policy::MockDevicePolicy>* device_policy =
new NiceMock<policy::MockDevicePolicy>;
EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
mount->set_policy_provider(new policy::PolicyProvider(
std::unique_ptr<NiceMock<policy::MockDevicePolicy>>(device_policy)));
FilePath keyset_path =
shadow_root.Append(obfuscated_username).Append("master.0");
EXPECT_CALL(platform, FileExists(keyset_path)).WillOnce(Return(false));
FilePath salt_path = shadow_root.Append("salt");
int64_t salt_size = salt.size();
EXPECT_CALL(platform, FileExists(salt_path)).WillRepeatedly(Return(true));
EXPECT_CALL(platform, GetFileSize(salt_path, _))
.WillRepeatedly(DoAll(SetArgPointee<1>(salt_size), Return(true)));
EXPECT_CALL(platform, ReadFile(salt_path, _))
.WillRepeatedly(DoAll(SetArgPointee<1>(salt), Return(true)));
EXPECT_CALL(platform, ReadFileToSecureBlob(salt_path, _))
.WillRepeatedly(DoAll(SetArgPointee<1>(sec_salt), Return(true)));
EXPECT_CALL(platform, DirectoryExists(shadow_root))
.WillRepeatedly(Return(true));
mount->Init(&platform, &crypto, &timestamp_cache);
cryptohome::Crypto::PasswordToPasskey(password, sec_salt, &passkey);
Credentials local_credentials(username, passkey);
if (use_key_data) {
if (is_le_credential)
key_data.set_label("PIN");
local_credentials.set_key_data(key_data);
}
bool created;
// NOTE! This code gives us generated credentials for credentials tests NOT
// NOTE! golden credentials to test against. This means we won't see problems
// NOTE! if the credentials generation and checking code break together.
// TODO(wad,ellyjones) Add golden credential tests too.
// "Old" image path
EXPECT_CALL(platform, FileExists(image_path)).WillRepeatedly(Return(false));
// Use 'stat' failures to trigger default-allow the creation of the paths.
EXPECT_CALL(
platform,
Stat(Property(
&FilePath::value,
AnyOf("/home", "/home/root",
brillo::cryptohome::home::GetRootPath(username).value(),
"/home/user",
brillo::cryptohome::home::GetUserPath(username).value())),
_))
.WillRepeatedly(Return(false));
EXPECT_CALL(
platform,
Stat(Property(&FilePath::value,
AnyOf("/home/chronos",
MountHelper::GetNewUserPath(username).value())),
_))
.WillRepeatedly(Return(false));
EXPECT_CALL(platform, DirectoryExists(vault_path)).WillOnce(Return(false));
EXPECT_CALL(platform, DirectoryExists(vault_mount_path))
.WillOnce(Return(false));
EXPECT_CALL(platform, CreateDirectory(_)).WillRepeatedly(Return(true));
// Grab the generated credential
EXPECT_CALL(platform, WriteFileAtomicDurable(keyset_path, _, _))
.WillOnce(DoAll(SaveArg<1>(&credentials), Return(true)));
Mount::MountArgs mount_args;
mount_args.create_as_ecryptfs = force_ecryptfs;
mount->EnsureCryptohome(local_credentials, mount_args, &created);
DCHECK(created && credentials.size());
// Unmount succeeds. This is called when |mount| is destroyed.
ON_CALL(platform, Unmount(_, _, _)).WillByDefault(Return(true));
}
void TestUser::InjectKeyset(MockPlatform* platform, bool enumerate) {
// TODO(wad) Update to support multiple keys
EXPECT_CALL(*platform, FileExists(Property(&FilePath::value,
StartsWith(keyset_path.value()))))
.WillRepeatedly(Return(true));
EXPECT_CALL(*platform, ReadFile(keyset_path, _))
.WillRepeatedly(DoAll(SetArgPointee<1>(credentials), Return(true)));
if (enumerate) {
EXPECT_CALL(*platform, GetFileEnumerator(base_path, false, _))
.WillRepeatedly(Invoke([&](base::FilePath path, bool rec, int type) {
MockFileEnumerator* files = new NiceMock<MockFileEnumerator>();
// Single key.
files->AddFileEntry(keyset_path);
return files;
}));
}
}
void TestUser::InjectUserPaths(MockPlatform* platform,
uid_t chronos_uid,
gid_t chronos_gid,
gid_t chronos_access_gid,
gid_t daemon_gid,
bool is_ecryptfs) {
scoped_refptr<Mount> temp_mount = new Mount();
EXPECT_CALL(*platform, FileExists(image_path)).WillRepeatedly(Return(false));
#if BASE_VER < 780000
struct stat root_dir;
#else
base::stat_wrapper_t root_dir;
#endif
memset(&root_dir, 0, sizeof(root_dir));
root_dir.st_mode = S_IFDIR | S_ISVTX;
EXPECT_CALL(*platform,
Stat(AnyOf(mount_prefix, root_mount_prefix, user_mount_prefix,
root_mount_path, user_vault_path),
_))
.WillRepeatedly(DoAll(SetArgPointee<1>(root_dir), Return(true)));
// Avoid triggering vault migration. (Is there another test for that?)
#if BASE_VER < 780000
struct stat root_vault_dir;
#else
base::stat_wrapper_t root_vault_dir;
#endif
memset(&root_vault_dir, 0, sizeof(root_vault_dir));
root_vault_dir.st_mode = S_IFDIR | S_ISVTX;
root_vault_dir.st_uid = 0;
root_vault_dir.st_gid = daemon_gid;
EXPECT_CALL(*platform,
Stat(is_ecryptfs ? root_vault_path : root_vault_mount_path, _))
.WillRepeatedly(DoAll(SetArgPointee<1>(root_vault_dir), Return(true)));
#if BASE_VER < 780000
struct stat user_dir;
#else
base::stat_wrapper_t user_dir;
#endif
memset(&user_dir, 0, sizeof(user_dir));
user_dir.st_mode = S_IFDIR;
user_dir.st_uid = chronos_uid;
user_dir.st_gid = chronos_access_gid;
EXPECT_CALL(
*platform,
Stat(AnyOf(user_mount_path, MountHelper::GetNewUserPath(username)), _))
.WillRepeatedly(DoAll(SetArgPointee<1>(user_dir), Return(true)));
if (!is_ecryptfs) {
EXPECT_CALL(*platform,
Stat(Property(&FilePath::value,
StartsWith(user_vault_mount_path.value())),
_))
.WillRepeatedly(DoAll(SetArgPointee<1>(user_dir), Return(true)));
}
#if BASE_VER < 780000
struct stat chronos_dir;
#else
base::stat_wrapper_t chronos_dir;
#endif
memset(&chronos_dir, 0, sizeof(chronos_dir));
chronos_dir.st_mode = S_IFDIR;
chronos_dir.st_uid = chronos_uid;
chronos_dir.st_gid = chronos_gid;
EXPECT_CALL(*platform, Stat(FilePath("/home/chronos"), _))
.WillRepeatedly(DoAll(SetArgPointee<1>(chronos_dir), Return(true)));
EXPECT_CALL(*platform, DirectoryExists(Property(
&FilePath::value,
AnyOf(shadow_root.value(), mount_prefix.value(),
StartsWith(legacy_user_mount_path.value()),
StartsWith(vault_mount_path.value())))))
.WillRepeatedly(Return(true));
EXPECT_CALL(*platform, DirectoryExists(Property(
&FilePath::value, StartsWith(vault_path.value()))))
.WillRepeatedly(Return(is_ecryptfs));
FilePath new_user_path = MountHelper::GetNewUserPath(username);
EXPECT_CALL(*platform, DirectoryExists(Property(
&FilePath::value,
AnyOf(StartsWith(legacy_user_mount_path.value()),
StartsWith(vault_mount_path.value()),
StartsWith(user_mount_path.value()),
StartsWith(root_mount_path.value()),
StartsWith(new_user_path.value()),
StartsWith(keyset_path.value())))))
.WillRepeatedly(Return(true));
EXPECT_CALL(*platform,
SetGroupAccessible(
Property(&FilePath::value,
AnyOf(StartsWith(legacy_user_mount_path.value()),
StartsWith(user_vault_mount_path.value()))),
chronos_access_gid, _))
.WillRepeatedly(Return(true));
if (!is_ecryptfs) {
EXPECT_CALL(*platform, GetDirCryptoKeyState(vault_mount_path))
.WillRepeatedly(Return(dircrypto::KeyState::ENCRYPTED));
}
}
} // namespace cryptohome