blob: e73cba604c555d7ec7145fdfeea16e5c18dae53c [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 <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/memory/ref_counted.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/mock_crypto.h"
#include "cryptohome/mock_platform.h"
#include "cryptohome/mock_tpm.h"
#include "cryptohome/mount.h"
#include "cryptohome/user_oldest_activity_timestamp_cache.h"
#include "cryptohome/username_passkey.h"
#include "cryptohome/vault_keyset.h"
using base::FilePath;
using brillo::SecureBlob;
using ::testing::AnyOf;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::NiceMock;
using ::testing::Mock;
using ::testing::Property;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::SetArgPointee;
using ::testing::StartsWith;
using ::testing::_;
namespace cryptohome {
// struct TestUserInfo {
// const char* username;
// const char* password;
// bool create;
// };
const TestUserInfo kDefaultUsers[] = {
{"testuser0@invalid.domain", "zero", true},
{"testuser1@invalid.domain", "one", true},
{"testuser2@invalid.domain", "two", true},
{"testuser3@invalid.domain", "three", true},
{"testuser4@invalid.domain", "four", true},
{"testuser5@invalid.domain", "five", false},
{"testuser6@invalid.domain", "six", true},
{"testuser7@invalid.domain", "seven", true},
{"testuser8@invalid.domain", "eight", true},
{"testuser9@invalid.domain", "nine", true},
{"testuser10@invalid.domain", "ten", true},
{"testuser11@invalid.domain", "eleven", true},
{"testuser12@invalid.domain", "twelve", false},
{"testuser13@invalid.domain", "thirteen", true},
};
const size_t kDefaultUserCount = arraysize(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, ReadFile(path, _))
.WillRepeatedly(DoAll(SetArgPointee<1>(system_salt), Return(true)));
}
void MakeTests::InjectEphemeralSkeleton(MockPlatform* platform,
const FilePath& root,
bool exists) {
const FilePath skel = root.Append("skeleton");
EXPECT_CALL(*platform, CreateDirectory(
Property(&FilePath::value, StartsWith(skel.value()))))
.WillRepeatedly(Return(true));
EXPECT_CALL(
*platform,
SetOwnership(
Property(&FilePath::value, StartsWith(skel.value())), _, _, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(*platform,
DirectoryExists(
Property(&FilePath::value, StartsWith(skel.value()))))
.WillRepeatedly(Return(exists));
EXPECT_CALL(*platform,
FileExists(
Property(&FilePath::value, StartsWith(skel.value()))))
.WillRepeatedly(Return(exists));
if (!exists) {
EXPECT_CALL(*platform,
SetGroupAccessible(
Property(&FilePath::value, StartsWith(skel.value())),
_, _))
.WillRepeatedly(Return(true));
}
}
void TestUser::FromInfo(const struct TestUserInfo* info,
const FilePath& image_dir) {
username = info->username;
password = info->password;
create = info->create;
use_key_data = 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");
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");
keyset_path = base_path.Append("master.0");
salt_path = base_path.Append("master.0.salt");
user_salt.assign('A', PKCS5_SALT_LEN);
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;
salt.resize(system_salt->size());
memcpy(&salt.at(0), system_salt->c_str(), system_salt->size());
NiceMock<MockTpm> tpm;
NiceMock<MockPlatform> platform;
Crypto crypto(&platform);
crypto.set_use_tpm(false);
crypto.set_scrypt_max_encrypt_time(0.001);
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>;
mount->set_policy_provider(new policy::PolicyProvider(device_policy));
EXPECT_CALL(*device_policy, LoadPolicy())
.WillRepeatedly(Return(true));
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, DirectoryExists(shadow_root))
.WillRepeatedly(Return(true));
mount->Init(&platform, &crypto, &timestamp_cache);
cryptohome::Crypto::PasswordToPasskey(password,
salt,
&passkey);
UsernamePasskey up(username, passkey);
if (use_key_data) {
up.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",
mount->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(up, mount_args, &created);
DCHECK(created && credentials.size());
}
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) {
MockFileEnumerator* files = new MockFileEnumerator();
EXPECT_CALL(*platform, GetFileEnumerator(base_path, false, _))
.WillOnce(Return(files));
{
InSequence s;
// Single key.
EXPECT_CALL(*files, Next())
.WillOnce(Return(keyset_path));
EXPECT_CALL(*files, Next())
.WillOnce(Return(FilePath()));
}
}
}
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));
struct stat root_dir;
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?)
struct stat root_vault_dir;
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)));
struct stat user_dir;
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,
temp_mount->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)));
}
struct stat chronos_dir;
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 = temp_mount->GetNewUserPath(username);
EXPECT_CALL(*platform,
FileExists(
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