blob: 02cad6d8de37620a677fc94cc6503fb4c2ba509f [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.
#include <algorithm>
#include <base/bind.h>
#include <base/logging.h>
#include <base/stringprintf.h>
#include <chromeos/cryptohome.h>
#include "credentials.h"
#include "homedirs.h"
#include "mount.h"
#include "platform.h"
#include "username_passkey.h"
#include "user_oldest_activity_timestamp_cache.h"
#include "vault_keyset.h"
using chromeos::SecureBlob;
namespace cryptohome {
const char *kShadowRoot = "/home/.shadow";
const char *kEmptyOwner = "";
HomeDirs::HomeDirs()
: default_platform_(new Platform()),
platform_(default_platform_.get()),
shadow_root_(kShadowRoot),
default_timestamp_cache_(new UserOldestActivityTimestampCache()),
timestamp_cache_(default_timestamp_cache_.get()),
enterprise_owned_(false),
default_policy_provider_(new policy::PolicyProvider()),
policy_provider_(default_policy_provider_.get()),
default_crypto_(new Crypto(platform_)),
crypto_(default_crypto_.get()),
default_mount_factory_(new MountFactory()),
mount_factory_(default_mount_factory_.get()),
default_vault_keyset_factory_(new VaultKeysetFactory()),
vault_keyset_factory_(default_vault_keyset_factory_.get()) { }
HomeDirs::~HomeDirs() { }
bool HomeDirs::Init() {
LoadDevicePolicy();
if (!platform_->DirectoryExists(shadow_root_))
platform_->CreateDirectory(shadow_root_);
return GetSystemSalt(NULL);
}
bool HomeDirs::FreeDiskSpace() {
if (platform_->AmountOfFreeDiskSpace(shadow_root_) > kMinFreeSpace) {
return false;
}
// If ephemeral users are enabled, remove all cryptohomes except those
// currently mounted or belonging to the owner.
// |AreEphemeralUsers| will reload the policy to guarantee freshness.
if (AreEphemeralUsersEnabled()) {
RemoveNonOwnerCryptohomes();
return true;
}
// Clean Cache directories for every user (except current one).
DoForEveryUnmountedCryptohome(base::Bind(&HomeDirs::DeleteCacheCallback,
base::Unretained(this)));
if (platform_->AmountOfFreeDiskSpace(shadow_root_) >= kEnoughFreeSpace)
return true;
// Clean Cache directories for every user (except current one).
DoForEveryUnmountedCryptohome(base::Bind(&HomeDirs::DeleteGCacheTmpCallback,
base::Unretained(this)));
if (platform_->AmountOfFreeDiskSpace(shadow_root_) >= kEnoughFreeSpace)
return true;
// Initialize user timestamp cache if it has not been yet. Current
// user is not added now, but added on log out or during daily
// updates (UpdateCurrentUserActivityTimestamp()).
if (!timestamp_cache_->initialized()) {
timestamp_cache_->Initialize();
DoForEveryUnmountedCryptohome(base::Bind(
&HomeDirs::AddUserTimestampToCacheCallback,
base::Unretained(this)));
}
// Delete old users, the oldest first.
// Don't delete anyone if we don't know who the owner is.
std::string owner;
if (enterprise_owned_ || GetOwner(&owner)) {
const base::Time timestamp_threshold =
base::Time::Now() - old_user_last_activity_time_;
while (!timestamp_cache_->oldest_known_timestamp().is_null() &&
timestamp_cache_->oldest_known_timestamp() <= timestamp_threshold) {
FilePath deleted_user_dir = timestamp_cache_->RemoveOldestUser();
if (!enterprise_owned_) {
std::string obfuscated_username = deleted_user_dir.BaseName().value();
if (obfuscated_username == owner)
continue;
}
std::string mountdir = deleted_user_dir.Append(kMountDir).value();
std::string vaultdir = deleted_user_dir.Append(kVaultDir).value();
if (platform_->IsDirectoryMountedWith(mountdir, vaultdir)) {
LOG(INFO) << "Attempt to delete currently logged user. Skipped...";
} else {
LOG(INFO) << "Deleting old user " << deleted_user_dir.value();
platform_->DeleteFile(deleted_user_dir.value(), true);
if (platform_->AmountOfFreeDiskSpace(shadow_root_) >= kEnoughFreeSpace)
return true;
}
}
}
// TODO(glotov): do further cleanup.
return true;
}
void HomeDirs::LoadDevicePolicy() {
policy_provider_->Reload();
}
bool HomeDirs::AreEphemeralUsersEnabled() {
LoadDevicePolicy();
// If the policy cannot be loaded, default to non-ephemeral users.
bool ephemeral_users_enabled = false;
if (policy_provider_->device_policy_is_loaded())
policy_provider_->GetDevicePolicy().GetEphemeralUsersEnabled(
&ephemeral_users_enabled);
return ephemeral_users_enabled;
}
bool HomeDirs::AreCredentialsValid(const Credentials& creds) {
scoped_ptr<VaultKeyset> vk(vault_keyset_factory()->New(platform_, crypto_));
return GetValidKeyset(creds, vk.get());
}
bool HomeDirs::GetValidKeyset(const Credentials& creds, VaultKeyset* vk) {
if (!vk)
return false;
std::string owner;
std::string obfuscated = creds.GetObfuscatedUsername(system_salt_);
// |AreEphemeralUsers| will reload the policy to guarantee freshness.
if (AreEphemeralUsersEnabled() && GetOwner(&owner) && obfuscated != owner)
return false;
std::vector<int> key_indices;
if (!GetVaultKeysets(obfuscated, &key_indices)) {
LOG(WARNING) << "No valid keysets on disk for " << obfuscated;
return false;
}
SecureBlob passkey;
creds.GetPasskey(&passkey);
std::vector<int>::const_iterator iter = key_indices.begin();
for ( ;iter != key_indices.end(); ++iter) {
if (vk->Load(GetVaultKeysetPath(obfuscated, *iter)) &&
vk->Decrypt(passkey))
return true;
}
return false;
}
// TODO(wad) Figure out how this might fit in with vault_keyset.cc
bool HomeDirs::GetVaultKeysets(const std::string& obfuscated,
std::vector<int>* keysets) const {
CHECK(keysets);
std::string user_dir = FilePath(shadow_root_).Append(obfuscated).value();
scoped_ptr<FileEnumerator> file_enumerator(
platform_->GetFileEnumerator(user_dir, false,
FileEnumerator::FILES));
std::string next_path;
while (!(next_path = file_enumerator->Next()).empty()) {
std::string file_name = FilePath(next_path).BaseName().value();
// Scan for "master." files.
if (file_name.find(kKeyFile, 0, strlen(kKeyFile) == std::string::npos))
continue;
char *end = NULL;
std::string index_str = file_name.substr(strlen(kKeyFile));
const char * index_c_str = index_str.c_str();
long index = strtol(index_c_str, &end, 10);
// Ensure the entire suffix is consumed.
if (end && *end != '\0')
continue;
// The test below will catch all strtol(3) error conditions.
if (index < 0 || index >= kKeyFileMax) {
LOG(ERROR) << "Invalid key file range: " << index;
continue;
}
keysets->push_back(static_cast<int>(index));
}
// Ensure it is sorted numerically and not lexigraphically.
std::sort(keysets->begin(), keysets->end());
return keysets->size() != 0;
}
bool HomeDirs::AddKeyset(const Credentials& existing_credentials,
const SecureBlob& new_passkey,
int* index) {
// TODO(wad) Determine how to best bubble up the failures MOUNT_ERROR
// encapsulate wrt the TPM behavior.
std::string obfuscated = existing_credentials.GetObfuscatedUsername(
system_salt_);
scoped_ptr<VaultKeyset> vk(vault_keyset_factory()->New(platform_, crypto_));
if (!GetValidKeyset(existing_credentials, vk.get())) {
LOG(WARNING) << "AddKeyset: authentication failed";
return false;
}
// Walk the namespace looking for the first free spot.
// Optimizations can come later.
// Note, nothing is stopping simultaenous access to these files
// or enforcing mandatory locking.
int new_index = 0;
FILE* vk_file = NULL;
std::string vk_path;
for ( ; new_index < kKeyFileMax; ++new_index) {
vk_path = GetVaultKeysetPath(obfuscated, new_index);
// Rely on fopen()'s O_EXCL|O_CREAT behavior to fail
// repeatedly until there is an opening.
// TODO(wad) Add a clean-up-0-byte-keysets helper to c-home startup
vk_file = platform_->OpenFile(vk_path, "wx");
if (vk_file) // got one
break;
}
if (!vk_file) {
LOG(WARNING) << "Failed to find an available keyset slot";
return false;
}
// Once the file has been claimed, we can release the handle.
platform_->CloseFile(vk_file);
// Repersist the VK with the new creds.
bool added = true;
if (!vk->Encrypt(new_passkey) || !vk->Save(vk_path)) {
LOG(WARNING) << "Failed to encrypt or write the new keyset";
added = false;
platform_->DeleteFile(vk_path, false);
} else {
*index = new_index;
}
return added;
}
bool HomeDirs::ForceRemoveKeyset(const std::string& obfuscated, int index) {
// Note, external callers should check credentials.
if (index < 0 || index >= kKeyFileMax)
return false;
std::string path = GetVaultKeysetPath(obfuscated, index);
if (!platform_->FileExists(path)) {
LOG(WARNING) << "ForceRemoveKeyset: keyset " << index << " for "
<< obfuscated << " does not exist";
// Since it doesn't exist, then we're done.
return true;
}
// TODO(wad) Add file zeroing here or centralize with other code.
return platform_->DeleteFile(path, false);
}
bool HomeDirs::MoveKeyset(const std::string& obfuscated, int src, int dst) {
if (src < 0 || dst < 0 || src >= kKeyFileMax || dst >= kKeyFileMax)
return false;
std::string src_path = GetVaultKeysetPath(obfuscated, src);
std::string dst_path = GetVaultKeysetPath(obfuscated, dst);
if (!platform_->FileExists(src_path))
return false;
if (platform_->FileExists(dst_path))
return false;
// Grab the destination exclusively
FILE* vk_file = platform_->OpenFile(dst_path, "wx");
if (!vk_file)
return false;
// The creation occurred so there's no reason to keep the handle.
platform_->CloseFile(vk_file);
if (!platform_->Rename(src_path, dst_path))
return false;
return true;
}
std::string HomeDirs::GetVaultKeysetPath(const std::string& obfuscated,
int index) const {
return StringPrintf("%s/%s/%s%d",
shadow_root_.c_str(),
obfuscated.c_str(),
kKeyFile,
index);
}
void HomeDirs::RemoveNonOwnerCryptohomesCallback(const FilePath& vault) {
if (!enterprise_owned_) { // Enterprise owned? Delete it all.
std::string owner;
if (!GetOwner(&owner) || // No owner? bail.
// Don't delete the owner's vault!
// TODO(wad,ellyjones) Add GetUser*Path-helpers
vault == FilePath(shadow_root_).Append(owner).Append(kVaultDir))
return;
}
// Once we're sure this is not the owner vault, delete it.
platform_->DeleteFile(vault.DirName().value(), true);
}
void HomeDirs::RemoveNonOwnerCryptohomes() {
std::string owner;
if (!enterprise_owned_ && !GetOwner(&owner))
return;
DoForEveryUnmountedCryptohome(base::Bind(
&HomeDirs::RemoveNonOwnerCryptohomesCallback,
base::Unretained(this)));
// TODO(ellyjones): is this valuable? These two directories should just be
// mountpoints.
RemoveNonOwnerDirectories(chromeos::cryptohome::home::GetUserPathPrefix());
RemoveNonOwnerDirectories(chromeos::cryptohome::home::GetRootPathPrefix());
}
void HomeDirs::DoForEveryUnmountedCryptohome(
const CryptohomeCallback& cryptohome_cb) {
std::vector<std::string> entries;
if (!platform_->EnumerateDirectoryEntries(shadow_root_, false, &entries))
return;
for (std::vector<std::string>::iterator it = entries.begin();
it != entries.end(); ++it) {
FilePath path(*it);
const std::string dir_name = path.BaseName().value();
if (!chromeos::cryptohome::home::IsSanitizedUserName(dir_name))
continue;
std::string vault_path = path.Append(kVaultDir).value();
std::string mount_path = path.Append(kMountDir).value();
if (!platform_->DirectoryExists(vault_path)) {
continue;
}
if (platform_->IsDirectoryMountedWith(mount_path, vault_path)) {
continue;
}
cryptohome_cb.Run(FilePath(vault_path));
}
}
void HomeDirs::DeleteDirectoryContents(const FilePath& dir) {
scoped_ptr<FileEnumerator> subdir_enumerator(platform_->GetFileEnumerator(
dir.value(),
false,
static_cast<FileEnumerator::FileType>(
FileEnumerator::FILES |
FileEnumerator::DIRECTORIES |
FileEnumerator::SHOW_SYM_LINKS)));
for (std::string subdir_path = subdir_enumerator->Next();
!subdir_path.empty();
subdir_path = subdir_enumerator->Next()) {
platform_->DeleteFile(subdir_path, true);
}
}
void HomeDirs::RemoveNonOwnerDirectories(const FilePath& prefix) {
std::vector<std::string> dirents;
if (!platform_->EnumerateDirectoryEntries(prefix.value(), false, &dirents))
return;
std::string owner;
if (!enterprise_owned_ && !GetOwner(&owner))
return;
for (std::vector<std::string>::iterator it = dirents.begin();
it != dirents.end(); ++it) {
FilePath path(*it);
const std::string basename = path.BaseName().value();
if (!enterprise_owned_ && !strcasecmp(basename.c_str(), owner.c_str()))
continue; // Skip the owner's directory.
if (!chromeos::cryptohome::home::IsSanitizedUserName(basename))
continue; // Skip any directory whose name is not an obfuscated user
// name.
if (platform_->IsDirectoryMounted(path.value()))
continue; // Skip any directory that is currently mounted.
platform_->DeleteFile(path.value(), true);
}
}
void HomeDirs::DeleteCacheCallback(const FilePath& vault) {
const FilePath cache = vault.Append(kUserHomeSuffix).Append(kCacheDir);
LOG(WARNING) << "Deleting Cache " << cache.value();
DeleteDirectoryContents(cache);
}
void HomeDirs::DeleteGCacheTmpCallback(const FilePath& vault) {
const FilePath gcachetmp = vault.Append(kUserHomeSuffix).Append(kGCacheDir)
.Append(kGCacheVersionDir).Append(kGCacheTmpDir);
LOG(WARNING) << "Deleting GCache " << gcachetmp.value();
DeleteDirectoryContents(gcachetmp);
}
void HomeDirs::AddUserTimestampToCacheCallback(const FilePath& vault) {
const FilePath user_dir = vault.DirName();
const std::string obfuscated_username = user_dir.BaseName().value();
// Add a timestamp for every key.
std::vector<int> key_indices;
// Failure is okay since the loop falls through.
GetVaultKeysets(obfuscated_username, &key_indices);
scoped_ptr<VaultKeyset> keyset(
vault_keyset_factory()->New(platform_, crypto_));
std::vector<int>::const_iterator iter = key_indices.begin();
// Collect the most recent time for a given user by walking all
// vaults. This avoids trying to keep them in sync atomically.
// TODO(wad,?) Move non-key vault metadata to a standalone file.
base::Time timestamp = base::Time();
for ( ;iter != key_indices.end(); ++iter) {
if (LoadVaultKeysetForUser(obfuscated_username, *iter, keyset.get()) &&
keyset->serialized().has_last_activity_timestamp()) {
const base::Time t = base::Time::FromInternalValue(
keyset->serialized().last_activity_timestamp());
if (t > timestamp)
timestamp = t;
}
}
if (!timestamp.is_null()) {
timestamp_cache_->AddExistingUser(user_dir, timestamp);
} else {
timestamp_cache_->AddExistingUserNotime(user_dir);
}
}
bool HomeDirs::LoadVaultKeysetForUser(const std::string& obfuscated_user,
int index,
VaultKeyset* keyset) const {
// Load the encrypted keyset
std::string user_key_file = GetVaultKeysetPath(obfuscated_user, index);
// We don't have keys yet, so just load it.
// TODO(wad) Move to passing around keysets and not serialized versions.
if (!keyset->Load(user_key_file)) {
LOG(ERROR) << "Failed to read keyset file for user " << obfuscated_user;
return false;
}
return true;
}
bool HomeDirs::GetPlainOwner(std::string* owner) {
if (!policy_provider_->device_policy_is_loaded())
return false;
policy_provider_->GetDevicePolicy().GetOwner(owner);
return true;
}
bool HomeDirs::GetOwner(std::string* owner) {
std::string plain_owner;
if (!GetPlainOwner(&plain_owner) || plain_owner.empty())
return false;
if (!GetSystemSalt(NULL))
return false;
*owner = UsernamePasskey(plain_owner.c_str(), chromeos::Blob())
.GetObfuscatedUsername(system_salt_);
return true;
}
bool HomeDirs::GetSystemSalt(SecureBlob* blob) {
FilePath salt_file = FilePath(shadow_root_).Append("salt");
if (!crypto_->GetOrCreateSalt(salt_file, CRYPTOHOME_DEFAULT_SALT_LENGTH,
false, &system_salt_)) {
LOG(ERROR) << "Failed to create system salt.";
return false;
}
if (blob)
*blob = system_salt_;
return true;
}
bool HomeDirs::Remove(const std::string& username) {
UsernamePasskey passkey(username.c_str(), SecureBlob("", 0));
std::string obfuscated = passkey.GetObfuscatedUsername(system_salt_);
FilePath user_dir = FilePath(shadow_root_).Append(obfuscated);
FilePath user_path = chromeos::cryptohome::home::GetUserPath(username);
FilePath root_path = chromeos::cryptohome::home::GetRootPath(username);
return platform_->DeleteFile(user_dir.value(), true) &&
platform_->DeleteFile(user_path.value(), true) &&
platform_->DeleteFile(root_path.value(), true);
}
bool HomeDirs::Migrate(const Credentials& newcreds,
const SecureBlob& oldkey) {
SecureBlob newkey;
newcreds.GetPasskey(&newkey);
UsernamePasskey oldcreds(newcreds.username().c_str(), oldkey);
scoped_refptr<Mount> mount = mount_factory_->New();
mount->set_platform(platform_);
mount->set_crypto(crypto_);
mount->Init();
std::string obfuscated = newcreds.GetObfuscatedUsername(system_salt_);
if (!mount->MountCryptohome(oldcreds, Mount::MountArgs(), NULL)) {
LOG(ERROR) << "Migrate: Mount failed";
// Fail as early as possible. Note that we don't have to worry about leaking
// this mount - Mount unmounts itself if it's still mounted in the
// destructor.
return false;
}
int key_index = mount->CurrentKey();
if (key_index == -1) {
LOG(ERROR) << "Attempted migration of key-less mount.";
return false;
}
int new_key_index = -1;
if (!AddKeyset(oldcreds, newkey, &new_key_index)) {
LOG(ERROR) << "Failed to add the new keyset";
return false;
}
SecureBlob old_auth_data;
SecureBlob auth_data;
std::string username = newcreds.username();
FilePath salt_file = GetChapsTokenSaltPath(username);
if (!crypto_->PasskeyToTokenAuthData(newkey, salt_file, &auth_data) ||
!crypto_->PasskeyToTokenAuthData(oldkey, salt_file, &old_auth_data)) {
// On failure, drop the newly added keyset so the user can try again.
ForceRemoveKeyset(obfuscated, new_key_index);
return false;
}
chaps_client_.ChangeTokenAuthData(
GetChapsTokenDir(username),
old_auth_data,
auth_data);
// Drop the old keyset
if (!ForceRemoveKeyset(obfuscated, key_index)) {
LOG(ERROR) << "Migrate: unable to delete the old keyset: " << key_index;
// TODO(wad) Should we zero it or move it into space?
// Fallthrough
}
// Put the new one in its slot.
if (!MoveKeyset(obfuscated, new_key_index, key_index)) {
LOG(ERROR) << "Migrate: failed to move the new key to the old slot";
// This is bad, but non-terminal since we have a valid, migrated key.
key_index = new_key_index;
}
// Remove all other keysets during a "migration".
std::vector<int> key_indices;
if (!GetVaultKeysets(obfuscated, &key_indices)) {
LOG(WARNING) << "Failed to enumerate keysets after adding one. Weird.";
// Fallthrough: The user is migrated, but something else changed keys.
}
std::vector<int>::const_iterator iter = key_indices.begin();
for ( ;iter != key_indices.end(); ++iter) {
if (*iter == key_index)
continue;
LOG(INFO) << "Removing keyset " << *iter << " due to migration.";
ForceRemoveKeyset(obfuscated, *iter); // Failure is ok.
}
return true;
}
namespace {
const char *kChapsDaemonName = "chaps";
const char *kChapsDirName = ".chaps";
const char *kChapsSaltName = "auth_data_salt";
}
FilePath HomeDirs::GetChapsTokenDir(const std::string& user) const {
return chromeos::cryptohome::home::GetDaemonPath(user, kChapsDaemonName);
}
FilePath HomeDirs::GetLegacyChapsTokenDir(const std::string& user) const {
return chromeos::cryptohome::home::GetUserPath(user).Append(kChapsDirName);
}
FilePath HomeDirs::GetChapsTokenSaltPath(const std::string& user) const {
return GetChapsTokenDir(user).Append(kChapsSaltName);
}
} // namespace cryptohome