blob: 8a4cac3840d74c711f81916bec624f113a00dc4b [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/filesystem_layout.h"
#include "cryptohome/mock_crypto.h"
#include "cryptohome/mock_platform.h"
#include "cryptohome/mock_tpm.h"
#include "cryptohome/storage/mount.h"
#include "cryptohome/storage/mount_helper.h"
#include "cryptohome/storage/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 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);
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) {
CHECK(brillo::cryptohome::home::GetSystemSalt());
EXPECT_CALL(*platform, FileExists(SaltFile())).WillRepeatedly(Return(true));
EXPECT_CALL(*platform, GetFileSize(SaltFile(), _))
.WillRepeatedly(
DoAll(SetArgPointee<1>(system_salt.size()), Return(true)));
EXPECT_CALL(*platform, ReadFileToSecureBlob(SaltFile(), _))
.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) {
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));
base_path = ShadowRoot().Append(obfuscated_username);
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_disable_logging_for_testing(/*disable=*/true);
CryptoLib::SetScryptTestingParams();
UserOldestActivityTimestampCache timestamp_cache;
NiceMock<policy::MockDevicePolicy>* device_policy =
new NiceMock<policy::MockDevicePolicy>;
EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
InitializeFilesystemLayout(&platform, &crypto, nullptr);
KeysetManagement keyset_management(&platform, &crypto, sec_salt,
std::make_unique<VaultKeysetFactory>());
HomeDirs homedirs(
&platform, &keyset_management, sec_salt, &timestamp_cache,
std::make_unique<policy::PolicyProvider>(
std::unique_ptr<policy::MockDevicePolicy>(device_policy)));
scoped_refptr<Mount> mount = new Mount(&platform, &homedirs);
FilePath keyset_path =
ShadowRoot().Append(obfuscated_username).Append("master.0");
FilePath salt_path = SaltFile();
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(ShadowRoot()))
.WillRepeatedly(Return(true));
platform.GetFake()->SetStandardUsersAndGroups();
mount->Init();
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);
}
// 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.
// 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, CreateDirectory(_)).WillRepeatedly(Return(true));
// Grab the generated credential
EXPECT_CALL(platform, WriteFileAtomicDurable(keyset_path, _, _))
.WillOnce(DoAll(SaveArg<1>(&credentials), Return(true)));
ASSERT_TRUE(homedirs.Create(local_credentials.username()));
ASSERT_TRUE(mount->PrepareCryptohome(obfuscated_username, force_ecryptfs));
ASSERT_TRUE(keyset_management.AddInitialKeyset(local_credentials,
/*dircrypto_v2=*/false));
DCHECK(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)));
EXPECT_CALL(*platform, ReadFile(timestamp_path, _))
.WillRepeatedly(Return(false));
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();
base::stat_wrapper_t 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?)
base::stat_wrapper_t 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)));
base::stat_wrapper_t 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, 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)));
}
base::stat_wrapper_t 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(ShadowRoot().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