blob: b8ccf6e257833b07939001daa273eb294d048897 [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 "cryptohome/service.h"
#if USE_TPM2
#include "cryptohome/bootlockbox/boot_lockbox_client.h"
#endif // USE_TPM2
#include <functional>
#include <base/bind.h>
#include <base/callback.h>
#include <base/command_line.h>
#include <base/files/file_util.h>
#include <base/json/json_writer.h>
#include <base/logging.h>
#include <base/message_loop/message_pump_type.h>
#include <base/optional.h>
#include <base/stl_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/strings/sys_string_conversions.h>
#include <base/system/sys_info.h>
#include <base/task/current_thread.h>
#include <base/time/time.h>
#include <base/values.h>
#include <brillo/cryptohome.h>
#include <brillo/glib/dbus.h>
#include <brillo/secure_blob.h>
#include <chaps/isolate.h>
#include <chaps/token_manager_client.h>
#include <chromeos/constants/cryptohome.h>
#include <dbus/bus.h>
#include <dbus/dbus.h>
#include <glib-unix.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <algorithm>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "cryptohome/bootlockbox/boot_attributes.h"
#include "cryptohome/bootlockbox/boot_lockbox.h"
#include "cryptohome/challenge_credentials/challenge_credentials_helper_impl.h"
#include "cryptohome/credentials.h"
#include "cryptohome/crypto.h"
#include "cryptohome/cryptohome_common.h"
#include "cryptohome/cryptohome_event_source.h"
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/cryptolib.h"
#include "cryptohome/dbus_transition.h"
#include "cryptohome/filesystem_layout.h"
#include "cryptohome/firmware_management_parameters.h"
#include "cryptohome/glib_transition.h"
#include "cryptohome/install_attributes.h"
#include "cryptohome/interface.h"
#include "cryptohome/key.pb.h"
#include "cryptohome/key_challenge_service.h"
#include "cryptohome/key_challenge_service_factory.h"
#include "cryptohome/key_challenge_service_factory_impl.h"
#include "cryptohome/platform.h"
#include "cryptohome/rpc.pb.h"
#include "cryptohome/service_distributed.h"
#include "cryptohome/stateful_recovery.h"
#include "cryptohome/storage/disk_cleanup.h"
#include "cryptohome/storage/mount.h"
#include "cryptohome/storage/user_oldest_activity_timestamp_cache.h"
#include "cryptohome/tpm.h"
#include "cryptohome/vault_keyset.pb.h"
using base::FilePath;
using brillo::Blob;
using brillo::BlobFromString;
using brillo::SecureBlob;
using brillo::cryptohome::home::SanitizeUserNameWithSalt;
namespace {
constexpr const char* kIgnoreParallelTaskNames[] = {"LowDiskCallback",
"UploadAlertsDataCallback"};
}
// Forcibly namespace the dbus-bindings generated server bindings instead of
// modifying the files afterward.
namespace cryptohome {
namespace gobject {
#include "bindings/cryptohome.dbusserver.h" // NOLINT(build/include_alpha)
} // namespace gobject
} // namespace cryptohome
namespace cryptohome {
namespace {
const std::string& GetAccountId(const AccountIdentifier& id) {
if (id.has_account_id()) {
return id.account_id();
}
return id.email();
}
void AddTaskObserverToThread(base::Thread* thread,
MountThreadObserver* task_observer) {
// Since CurrentThread::AddTaskObserver need to be executed in the same
// thread of that message loop. So we need to wrap it and post as a task.
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
thread->task_runner();
if (task_runner == nullptr) {
LOG(ERROR) << __func__ << ": The thread doesn't have task runner.";
return;
}
task_observer->PostTask();
task_runner->PostTask(
FROM_HERE,
base::BindOnce(
[](MountThreadObserver* task_observer) {
base::CurrentThread::Get().AddTaskObserver(task_observer);
},
base::Unretained(task_observer)));
}
// Returns whether the Chrome OS image is a test one.
bool IsOsTestImage() {
std::string chromeos_release_track;
if (!base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_TRACK",
&chromeos_release_track)) {
// Fall back to the safer assumption that we're not in a test image.
return false;
}
return base::StartsWith(chromeos_release_track, "test",
base::CompareCase::SENSITIVE);
}
// Whether the key can be used for lightweight challenge-response authentication
// check against the given user session.
bool KeyMatchesForLightweightChallengeResponseCheck(
const KeyData& key_data, const UserSession& session) {
DCHECK_EQ(key_data.type(), KeyData::KEY_TYPE_CHALLENGE_RESPONSE);
DCHECK_EQ(key_data.challenge_response_key_size(), 1);
if (session.key_data().type() != KeyData::KEY_TYPE_CHALLENGE_RESPONSE ||
session.key_data().label().empty() ||
session.key_data().label() != key_data.label())
return false;
if (session.key_data().challenge_response_key_size() != 1) {
// Using multiple challenge-response keys at once is currently unsupported.
return false;
}
if (session.key_data().challenge_response_key(0).public_key_spki_der() !=
key_data.challenge_response_key(0).public_key_spki_der()) {
LOG(WARNING) << "Public key mismatch for lightweight challenge-response "
"authentication check";
return false;
}
return true;
}
// Performs an attempt to mount a non-guest user.
MountError AttemptUserMount(const Credentials& credentials,
const Mount::MountArgs& mount_args,
scoped_refptr<UserSession> user_session) {
if (user_session->GetMount()->IsMounted()) {
return MOUNT_ERROR_MOUNT_POINT_BUSY;
}
if (mount_args.is_ephemeral) {
return user_session->MountEphemeral(credentials);
}
return user_session->MountVault(credentials, mount_args);
}
} // anonymous namespace
const char kMountThreadName[] = "MountThread";
const char kTpmInitStatusEventType[] = "TpmInitStatus";
const char kDircryptoMigrationProgressEventType[] =
"DircryptoMigrationProgress";
const char kAutoInitializeTpmSwitch[] = "auto_initialize_tpm";
class TpmInitStatus : public CryptohomeEventBase {
public:
TpmInitStatus(bool took_ownership, bool status)
: took_ownership_(took_ownership), status_(status) {}
~TpmInitStatus() override = default;
const char* GetEventName() const override { return kTpmInitStatusEventType; }
bool get_took_ownership() { return took_ownership_; }
bool get_status() { return status_; }
private:
bool took_ownership_;
bool status_;
};
class DircryptoMigrationProgress : public CryptohomeEventBase {
public:
DircryptoMigrationProgress(DircryptoMigrationStatus status,
uint64_t current_bytes,
uint64_t total_bytes)
: status_(status),
current_bytes_(current_bytes),
total_bytes_(total_bytes) {}
DircryptoMigrationProgress(const DircryptoMigrationProgress&) = delete;
DircryptoMigrationProgress& operator=(const DircryptoMigrationProgress&) =
delete;
~DircryptoMigrationProgress() override = default;
const char* GetEventName() const override {
return kDircryptoMigrationProgressEventType;
}
DircryptoMigrationStatus status() const { return status_; }
uint64_t current_bytes() const { return current_bytes_; }
uint64_t total_bytes() const { return total_bytes_; }
private:
DircryptoMigrationStatus status_;
uint64_t current_bytes_;
uint64_t total_bytes_;
};
void MountThreadObserver::PostTask() {
parallel_task_count_ += 1;
}
void MountThreadObserver::WillProcessTask(const base::PendingTask& pending_task,
bool was_blocked_or_low_priority) {
// Task name will be equal to the task handler name
std::string task_name = pending_task.posted_from.function_name();
ReportAsyncDbusRequestInqueueTime(
task_name, base::TimeTicks::Now() - pending_task.delayed_run_time);
}
void MountThreadObserver::DidProcessTask(
const base::PendingTask& pending_task) {
for (const char* name : kIgnoreParallelTaskNames) {
if (pending_task.posted_from.function_name() == name) {
return;
}
}
parallel_task_count_ -= 1;
}
int MountThreadObserver::GetParallelTaskCount() const {
return parallel_task_count_;
}
Service::Service()
: loop_(NULL),
cryptohome_(NULL),
system_salt_(),
default_platform_(new Platform()),
platform_(default_platform_.get()),
default_crypto_(new Crypto(platform_)),
crypto_(default_crypto_.get()),
tpm_(nullptr),
tpm_init_(nullptr),
fingerprint_manager_(nullptr),
default_pkcs11_init_(new Pkcs11Init()),
pkcs11_init_(default_pkcs11_init_.get()),
initialize_tpm_(true),
mount_thread_(kMountThreadName),
async_complete_signal_(-1),
async_data_complete_signal_(-1),
tpm_init_signal_(-1),
low_disk_space_signal_(-1),
dircrypto_migration_progress_signal_(-1),
low_disk_space_signal_was_emitted_(false),
event_source_(),
event_source_sink_(this),
default_install_attrs_(new cryptohome::InstallAttributes(NULL)),
install_attrs_(default_install_attrs_.get()),
reported_pkcs11_init_fail_(false),
enterprise_owned_(false),
user_timestamp_cache_(new UserOldestActivityTimestampCache()),
default_mount_factory_(new cryptohome::MountFactory()),
mount_factory_(default_mount_factory_.get()),
default_keyset_management_(nullptr),
keyset_management_(nullptr),
default_homedirs_(nullptr),
homedirs_(nullptr),
default_arc_disk_quota_(nullptr),
arc_disk_quota_(nullptr),
default_disk_cleanup_(nullptr),
disk_cleanup_(nullptr),
guest_user_(brillo::cryptohome::home::kGuestUserName),
force_ecryptfs_(true),
legacy_mount_(true),
bind_mount_downloads_(true),
public_mount_salt_(),
default_chaps_client_(new chaps::TokenManagerClient()),
chaps_client_(default_chaps_client_.get()),
boot_lockbox_(nullptr),
boot_attributes_(nullptr),
firmware_management_parameters_(nullptr),
low_disk_notification_period_ms_(kLowDiskNotificationPeriodMS),
upload_alerts_period_ms_(kUploadAlertsPeriodMS),
ownership_callback_has_run_(false) {}
Service::~Service() {
mount_thread_.Stop();
if (loop_) {
g_main_loop_unref(loop_);
}
if (cryptohome_) {
g_object_unref(cryptohome_);
}
}
void Service::StopTasks() {
LOG(INFO) << "Stopping cryptohome task processing.";
if (loop_) {
g_main_loop_quit(loop_);
}
// It is safe to call Stop() multiple times
mount_thread_.Stop();
}
Service* Service::CreateDefault() {
return new ServiceDistributed();
}
static bool PrefixPresent(const std::vector<FilePath>& prefixes,
const std::string path) {
for (const auto& prefix : prefixes)
if (base::StartsWith(path, prefix.value(),
base::CompareCase::INSENSITIVE_ASCII))
return true;
return false;
}
bool Service::UnloadPkcs11Tokens(const std::vector<FilePath>& exclude) {
SecureBlob isolate =
chaps::IsolateCredentialManager::GetDefaultIsolateCredential();
std::vector<std::string> tokens;
if (!chaps_client_->GetTokenList(isolate, &tokens))
return false;
for (size_t i = 0; i < tokens.size(); ++i) {
if (tokens[i] != chaps::kSystemTokenPath &&
!PrefixPresent(exclude, tokens[i])) {
LOG(INFO) << "Cleaning up PKCS #11 token: " << tokens[i];
chaps_client_->UnloadToken(isolate, FilePath(tokens[i]));
}
}
return true;
}
CryptohomeErrorCode Service::MountErrorToCryptohomeError(
const MountError code) const {
switch (code) {
case MOUNT_ERROR_NONE:
return CRYPTOHOME_ERROR_NOT_SET;
case MOUNT_ERROR_FATAL:
return CRYPTOHOME_ERROR_MOUNT_FATAL;
case MOUNT_ERROR_KEY_FAILURE:
return CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED;
case MOUNT_ERROR_MOUNT_POINT_BUSY:
return CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY;
case MOUNT_ERROR_TPM_COMM_ERROR:
return CRYPTOHOME_ERROR_TPM_COMM_ERROR;
case MOUNT_ERROR_UNPRIVILEGED_KEY:
return CRYPTOHOME_ERROR_AUTHORIZATION_KEY_DENIED;
case MOUNT_ERROR_TPM_DEFEND_LOCK:
return CRYPTOHOME_ERROR_TPM_DEFEND_LOCK;
case MOUNT_ERROR_TPM_UPDATE_REQUIRED:
return CRYPTOHOME_ERROR_TPM_UPDATE_REQUIRED;
case MOUNT_ERROR_USER_DOES_NOT_EXIST:
return CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND;
case MOUNT_ERROR_TPM_NEEDS_REBOOT:
return CRYPTOHOME_ERROR_TPM_NEEDS_REBOOT;
case MOUNT_ERROR_OLD_ENCRYPTION:
return CRYPTOHOME_ERROR_MOUNT_OLD_ENCRYPTION;
case MOUNT_ERROR_PREVIOUS_MIGRATION_INCOMPLETE:
return CRYPTOHOME_ERROR_MOUNT_PREVIOUS_MIGRATION_INCOMPLETE;
case MOUNT_ERROR_VAULT_UNRECOVERABLE:
return CRYPTOHOME_ERROR_VAULT_UNRECOVERABLE;
case MOUNT_ERROR_RECREATED:
return CRYPTOHOME_ERROR_NOT_SET;
default:
return CRYPTOHOME_ERROR_MOUNT_FATAL;
}
}
void Service::PostTask(const base::Location& from_here,
base::OnceClosure task) {
mount_thread_observer_.PostTask();
int task_count = mount_thread_observer_.GetParallelTaskCount();
if (task_count > 1) {
ReportParallelTasks(task_count);
}
mount_thread_.task_runner()->PostTask(from_here, std::move(task));
}
void Service::SendReply(DBusGMethodInvocation* context,
const BaseReply& reply) {
// DBusBlobReply will take ownership of the |reply_str|.
std::unique_ptr<std::string> reply_str(new std::string);
reply.SerializeToString(reply_str.get());
event_source_.AddEvent(
std::make_unique<DBusBlobReply>(context, reply_str.release()));
}
void Service::SendDBusErrorReply(DBusGMethodInvocation* context,
GQuark domain,
gint code,
const gchar* message) {
if (message) {
LOG(ERROR) << message;
}
GError* error = g_error_new_literal(domain, code, message);
event_source_.AddEvent(std::make_unique<DBusErrorReply>(context, error));
}
bool Service::FilterActiveMounts(
std::multimap<const FilePath, const FilePath>* mounts,
std::multimap<const FilePath, const FilePath>* active_mounts,
bool force) {
bool skipped = false;
std::set<const FilePath> children_to_preserve;
for (auto match = mounts->begin(); match != mounts->end();) {
auto curr = match;
bool keep = false;
// Walk each set of sources as one group since multimaps are key ordered.
for (; match != mounts->end() && match->first == curr->first; ++match) {
// Ignore known mounts.
{
base::AutoLock _lock(sessions_lock_);
for (const auto& session_pair : sessions_) {
if (session_pair.second->GetMount()->OwnsMountPoint(match->second)) {
keep = true;
// If !force, other mount points not owned scanned after should
// be preserved as well.
if (force)
break;
}
}
}
// Ignore mounts pointing to children of used mounts.
if (!force) {
if (children_to_preserve.find(match->second) !=
children_to_preserve.end()) {
keep = true;
skipped = true;
LOG(WARNING) << "Stale mount " << match->second.value() << " from "
<< match->first.value() << " is a just a child.";
}
}
// Optionally, ignore mounts with open files.
if (!keep && !force) {
std::vector<ProcessInformation> processes;
platform_->GetProcessesWithOpenFiles(match->second, &processes);
if (processes.size()) {
const std::vector<std::string> cmd_line = processes[0].get_cmd_line();
const std::string first_cmd =
(cmd_line.size() > 0 ? cmd_line[0] : "<empty>");
LOG(WARNING) << "Stale mount " << match->second.value() << " from "
<< match->first.value() << " has " << processes.size()
<< " active holders. First one " << first_cmd;
keep = true;
skipped = true;
}
}
}
if (keep) {
std::multimap<const FilePath, const FilePath> children;
LOG(WARNING) << "Looking for children of " << curr->first;
platform_->GetMountsBySourcePrefix(curr->first, &children);
for (const auto& child : children) {
children_to_preserve.insert(child.second);
}
active_mounts->insert(curr, match);
mounts->erase(curr, match);
}
}
return skipped;
}
void Service::GetEphemeralLoopDevicesMounts(
std::multimap<const FilePath, const FilePath>* mounts) {
std::multimap<const FilePath, const FilePath> loop_mounts;
platform_->GetLoopDeviceMounts(&loop_mounts);
const FilePath sparse_path =
FilePath(kEphemeralCryptohomeDir).Append(kSparseFileDir);
for (const auto& device : platform_->GetAttachedLoopDevices()) {
// Ephemeral mounts are mounts from a loop device with ephemeral sparse
// backing file.
if (sparse_path.IsParent(device.backing_file)) {
auto range = loop_mounts.equal_range(device.device);
mounts->insert(range.first, range.second);
}
}
}
bool Service::CleanUpStaleMounts(bool force) {
// This function is meant to aid in a clean recovery from a crashed or
// manually restarted cryptohomed. Cryptohomed may restart:
// 1. Before any mounts occur
// 2. While mounts are active
// 3. During an unmount
// In case #1, there should be no special work to be done.
// The best way to disambiguate #2 and #3 is to determine if there are
// any active open files on any stale mounts. If there are open files,
// then we've likely(*) resumed an active session. If there are not,
// the last cryptohome should have been unmounted.
// It's worth noting that a restart during active use doesn't impair
// other user session behavior, like CheckKey, because it doesn't rely
// exclusively on mount state.
//
// In the future, it may make sense to attempt to keep the MountMap
// persisted to disk which would make resumption much easier.
//
// (*) Relies on the expectation that all processes have been killed off.
std::multimap<const FilePath, const FilePath> shadow_mounts;
std::multimap<const FilePath, const FilePath> ephemeral_mounts;
platform_->GetMountsBySourcePrefix(ShadowRoot(), &shadow_mounts);
GetEphemeralLoopDevicesMounts(&ephemeral_mounts);
std::multimap<const FilePath, const FilePath> excluded;
bool skipped = FilterActiveMounts(&shadow_mounts, &excluded, force);
skipped |= FilterActiveMounts(&ephemeral_mounts, &excluded, force);
std::vector<FilePath> excluded_mount_points;
for (const auto& mount : excluded)
excluded_mount_points.push_back(mount.second);
UnloadPkcs11Tokens(excluded_mount_points);
// Unmount anything left.
for (const auto& match : shadow_mounts) {
LOG(WARNING) << "Lazily unmounting stale shadow mount: "
<< match.second.value() << " from " << match.first.value();
platform_->Unmount(match.second, true, nullptr);
}
// Attempt to clear the encryption key for the shadow directories once
// the mount has been unmounted. The encryption key needs to be cleared
// after all the unmounts are done to ensure that none of the existing
// submounts becomes inaccessible.
if (force && !shadow_mounts.empty()) {
// Attempt to clear fscrypt encryption keys for the shadow mounts.
for (const auto& match : shadow_mounts) {
if (!platform_->InvalidateDirCryptoKey(dircrypto::KeyReference(),
match.first)) {
LOG(WARNING) << "Failed to clear fscrypt keys for stale mount: "
<< match.first;
}
}
// Clear all keys in the user keyring for ecryptfs mounts.
if (!platform_->ClearUserKeyring()) {
LOG(WARNING) << "Failed to clear stale user keys.";
}
}
for (const auto& match : ephemeral_mounts) {
LOG(WARNING) << "Lazily unmounting stale ephemeral mount: "
<< match.second.value() << " from " << match.first.value();
platform_->Unmount(match.second, true, nullptr);
// Clean up destination directory for ephemeral mounts under ephemeral
// cryptohome dir.
if (base::StartsWith(match.first.value(), kLoopPrefix,
base::CompareCase::SENSITIVE) &&
FilePath(kEphemeralCryptohomeDir).IsParent(match.second)) {
platform_->DeletePathRecursively(match.second);
}
}
// TODO(chromium:781821): Add autotests for this case.
std::vector<Platform::LoopDevice> loop_devices =
platform_->GetAttachedLoopDevices();
const FilePath sparse_dir =
FilePath(kEphemeralCryptohomeDir).Append(kSparseFileDir);
std::vector<FilePath> stale_sparse_files;
platform_->EnumerateDirectoryEntries(sparse_dir, false /* is_recursive */,
&stale_sparse_files);
for (const auto& device : loop_devices) {
// Check whether it's created from an ephemeral sparse file.
if (!sparse_dir.IsParent(device.backing_file))
continue;
if (excluded.count(device.device) == 0) {
LOG(WARNING) << "Detaching stale loop device: " << device.device.value();
if (!platform_->DetachLoop(device.device)) {
ReportCryptohomeError(kEphemeralCleanUpFailed);
PLOG(ERROR) << "Can't detach stale loop: " << device.device.value();
}
} else {
// Remove if it's a non-stale loop device.
stale_sparse_files.erase(
std::remove(stale_sparse_files.begin(), stale_sparse_files.end(),
device.backing_file),
stale_sparse_files.end());
}
}
for (const auto& file : stale_sparse_files) {
LOG(WARNING) << "Deleting stale ephemeral backing sparse file: "
<< file.value();
if (!platform_->DeleteFile(file)) {
ReportCryptohomeError(kEphemeralCleanUpFailed);
PLOG(ERROR) << "Failed to clean up ephemeral sparse file: "
<< file.value();
}
}
return skipped;
}
bool Service::CleanUpHiddenMounts() {
bool ok = true;
base::AutoLock _lock(sessions_lock_);
for (auto it = sessions_.begin(); it != sessions_.end();) {
scoped_refptr<UserSession> session = it->second;
if (session->GetMount()->IsMounted() &&
session->GetMount()->IsShadowOnly()) {
ok = ok && session->Unmount();
it = sessions_.erase(it);
} else {
++it;
}
}
return ok;
}
bool Service::Initialize() {
bool result = true;
if (!tpm_) {
tpm_ = Tpm::GetSingleton();
}
if (!tpm_init_ && initialize_tpm_) {
default_tpm_init_.reset(new TpmInit(tpm_, platform_));
tpm_init_ = default_tpm_init_.get();
}
if (!boot_lockbox_) {
default_boot_lockbox_.reset(new BootLockbox(tpm_, platform_, crypto_));
boot_lockbox_ = default_boot_lockbox_.get();
}
if (!boot_attributes_) {
default_boot_attributes_.reset(
new BootAttributes(boot_lockbox_, platform_));
boot_attributes_ = default_boot_attributes_.get();
}
if (!firmware_management_parameters_) {
default_firmware_management_params_.reset(
new FirmwareManagementParameters(tpm_));
firmware_management_parameters_ = default_firmware_management_params_.get();
}
if (!crypto_->Init(tpm_init_))
return false;
if (!InitializeFilesystemLayout(platform_, crypto_, &system_salt_)) {
LOG(ERROR) << "Failed to initialize filesystem layout.";
return false;
}
if (!keyset_management_) {
default_keyset_management_ = std::make_unique<KeysetManagement>(
platform_, crypto_, system_salt_,
std::make_unique<VaultKeysetFactory>());
keyset_management_ = default_keyset_management_.get();
}
if (!homedirs_) {
default_homedirs_ =
std::make_unique<HomeDirs>(platform_, keyset_management_, system_salt_,
user_timestamp_cache_.get(),
std::make_unique<policy::PolicyProvider>());
homedirs_ = default_homedirs_.get();
}
if (!arc_disk_quota_) {
default_arc_disk_quota_ = std::make_unique<ArcDiskQuota>(
homedirs_, platform_, base::FilePath(kArcDiskHome));
arc_disk_quota_ = default_arc_disk_quota_.get();
}
// Initialize ARC Disk Quota Service.
arc_disk_quota_->Initialize();
if (!disk_cleanup_) {
default_disk_cleanup_ = std::make_unique<DiskCleanup>(
platform_, homedirs_, user_timestamp_cache_.get());
disk_cleanup_ = default_disk_cleanup_.get();
}
// Install the type-info for the service with dbus.
dbus_g_object_type_install_info(gobject::cryptohome_get_type(),
&gobject::dbus_glib_cryptohome_object_info);
if (!Reset()) {
result = false;
}
// Registers the signal callbacks so the loop can exit gracefully.
for (int sig : {SIGTERM, SIGINT, SIGHUP}) {
g_unix_signal_add(sig, ShutdownService, this);
}
base::Thread::Options options;
options.message_pump_type = base::MessagePumpType::IO;
mount_thread_.StartWithOptions(options);
// Add task observer, message_loop is only available after the thread start.
// We can only add observer inside the thread.
AddTaskObserverToThread(&mount_thread_, &mount_thread_observer_);
// Clean up any unreferenced mountpoints at startup.
CleanUpStaleMounts(false);
// This ownership taken signal registration should be done before any
// Tpm::IsOwned() call so that Tpm can cache and update the ownership state
// correctly without keeping requesting for the TPM status.
ConnectOwnershipTakenSignal();
// If the TPM is unowned or doesn't exist, it's safe for
// this function to be called again. However, it shouldn't
// be called across multiple threads in parallel.
InitializeInstallAttributes();
AttestationInitialize();
async_complete_signal_ =
g_signal_lookup("async_call_status", gobject::cryptohome_get_type());
if (!async_complete_signal_) {
async_complete_signal_ =
g_signal_new("async_call_status", gobject::cryptohome_get_type(),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, nullptr, G_TYPE_NONE, 3,
G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_INT);
}
async_data_complete_signal_ = g_signal_lookup("async_call_status_with_data",
gobject::cryptohome_get_type());
if (!async_data_complete_signal_) {
async_data_complete_signal_ = g_signal_new(
"async_call_status_with_data", gobject::cryptohome_get_type(),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, nullptr, G_TYPE_NONE, 3, G_TYPE_INT,
G_TYPE_BOOLEAN, DBUS_TYPE_G_UCHAR_ARRAY);
}
tpm_init_signal_ =
g_signal_lookup("tpm_init_status", gobject::cryptohome_get_type());
if (!tpm_init_signal_) {
tpm_init_signal_ =
g_signal_new("tpm_init_status", gobject::cryptohome_get_type(),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, nullptr, G_TYPE_NONE, 3,
G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
}
low_disk_space_signal_ =
g_signal_lookup("low_disk_space", gobject::cryptohome_get_type());
if (!low_disk_space_signal_) {
low_disk_space_signal_ = g_signal_new(
"low_disk_space", gobject::cryptohome_get_type(), G_SIGNAL_RUN_LAST, 0,
NULL, NULL, nullptr, G_TYPE_NONE, 1, G_TYPE_UINT64);
}
dircrypto_migration_progress_signal_ = g_signal_lookup(
"dircrypto_migration_progress", gobject::cryptohome_get_type());
if (!dircrypto_migration_progress_signal_) {
dircrypto_migration_progress_signal_ = g_signal_new(
"dircrypto_migration_progress", gobject::cryptohome_get_type(),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, nullptr, G_TYPE_NONE, 3, G_TYPE_INT,
G_TYPE_UINT64, G_TYPE_UINT64);
}
// TODO(wad) Determine if this should only be called if
// tpm->IsEnabled() is true.
if (tpm_ && initialize_tpm_) {
tpm_init_->Init(
base::Bind(&Service::OwnershipCallback, base::Unretained(this)));
if (!SeedUrandom()) {
LOG(ERROR) << "FAILED TO SEED /dev/urandom AT START";
}
AttestationInitializeTpm();
if (tpm_init_->ShallInitialize() ||
base::CommandLine::ForCurrentProcess()->HasSwitch(
kAutoInitializeTpmSwitch)) {
tpm_init_->AsyncTakeOwnership();
}
}
last_user_activity_timestamp_time_ = platform_->GetCurrentTime();
// Clean up space on start (once).
PostTask(FROM_HERE,
base::Bind(&Service::DoAutoCleanup, base::Unretained(this)));
// Start scheduling periodic check for low-disk space and cleanup events.
// Subsequent events are scheduled by the callback itself.
PostTask(FROM_HERE,
base::Bind(&Service::LowDiskCallback, base::Unretained(this)));
// Start scheduling periodic TPM alerts upload to UMA. Subsequent events are
// scheduled by the callback itself.
PostTask(FROM_HERE, base::Bind(&Service::UploadAlertsDataCallback,
base::Unretained(this)));
// Create a FingerprintManager for talking to biod over dbus..
PostTask(FROM_HERE, base::Bind(&Service::CreateFingerprintManager,
base::Unretained(this)));
// TODO(keescook,ellyjones) Make this mock-able.
auto mountfn =
base::Bind(&Service::StatefulRecoveryMount, base::Unretained(this));
auto unmountfn =
base::Bind(&Service::StatefulRecoveryUnmount, base::Unretained(this));
auto isownerfn = base::Bind(&Service::IsOwner, base::Unretained(this));
StatefulRecovery recovery(platform_, mountfn, unmountfn, isownerfn);
if (recovery.Requested()) {
if (recovery.Recover())
LOG(INFO) << "A stateful recovery was performed successfully.";
recovery.PerformReboot();
}
boot_attributes_->Load();
return result;
}
bool Service::StatefulRecoveryMount(const std::string& username,
const std::string& passkey,
FilePath* out_home_path) {
gint error_code;
gboolean result;
GError* error = NULL;
if (!Mount(username.c_str(), passkey.c_str(), false, false, &error_code,
&result, &error) ||
!result) {
LOG(ERROR) << "Could not authenticate user '" << username
<< "' for stateful recovery: "
<< (error ? error->message : "[null]") << " (code:" << error_code
<< ")";
return false;
}
if (!GetMountPointForUser(username.c_str(), out_home_path)) {
LOG(ERROR) << "Mount point missing after successful mount call!?";
return false;
}
return true;
}
bool Service::StatefulRecoveryUnmount() {
gboolean result;
GError* error = NULL;
if (!Unmount(&result, &error) || !result) {
LOG(ERROR) << "Failed to unmount after stateful recovery: "
<< (error ? error->message : "[null]");
return false;
}
return true;
}
bool Service::IsOwner(const std::string& userid) {
std::string owner;
if (homedirs_->GetPlainOwner(&owner) && userid.length() && userid == owner)
return true;
return false;
}
void Service::InitializeInstallAttributes() {
// Don't reinitialize when install attributes are valid.
if (install_attrs_->status() == InstallAttributes::Status::kValid) {
return;
}
// The TPM owning instance may have changed since initialization.
// InstallAttributes can handle a NULL or !IsEnabled Tpm object.
install_attrs_->SetTpm(tpm_);
install_attrs_->Init(tpm_init_);
// Check if the machine is enterprise owned and report to mount_ then.
DetectEnterpriseOwnership();
}
void Service::DoInitializePkcs11(UserSession* session) {
bool still_mounted = false;
{
base::AutoLock _lock(sessions_lock_);
for (const auto& session_pair : sessions_) {
if (session_pair.second.get() == session) {
still_mounted = true;
}
}
}
if (!still_mounted) {
LOG(INFO) << "PKCS#11 initialization cancelled";
return;
}
if (session->GetMount()->IsMounted() &&
session->GetMount()->pkcs11_state() ==
cryptohome::Mount::kIsBeingInitialized) {
session->GetMount()->InsertPkcs11Token();
}
LOG(INFO) << "PKCS#11 initialization succeeded.";
session->GetMount()->set_pkcs11_state(cryptohome::Mount::kIsInitialized);
}
void Service::InitializePkcs11(UserSession* session) {
if (!session) {
LOG(ERROR) << "InitializePkcs11 called with NULL mount!";
return;
}
// Wait for ownership if there is a working TPM.
if (tpm_ && tpm_->IsEnabled() && !tpm_->IsOwned()) {
LOG(WARNING) << "TPM was not owned. TPM initialization call back will"
<< " handle PKCS#11 initialization.";
session->GetMount()->set_pkcs11_state(cryptohome::Mount::kIsWaitingOnTPM);
return;
}
// Ok, so the TPM is owned. Time to request asynchronous initialization of
// PKCS#11.
// Make sure cryptohome is mounted, otherwise all of this is for naught.
if (!session->GetMount()->IsMounted()) {
LOG(WARNING) << "PKCS#11 initialization requested but cryptohome is"
<< " not mounted.";
return;
}
// Reset PKCS#11 initialization status. A successful completion of
// MountTaskPkcs11_Init would set it in the service thread via NotifyEvent().
ReportTimerStart(kPkcs11InitTimer);
session->GetMount()->set_pkcs11_state(cryptohome::Mount::kIsBeingInitialized);
mount_thread_observer_.PostTask();
mount_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&Service::DoInitializePkcs11, base::Unretained(this),
base::Unretained(session)));
}
bool Service::SeedUrandom() {
brillo::Blob random;
if (!tpm_->GetRandomDataBlob(kDefaultRandomSeedLength, &random)) {
LOG(ERROR) << "Could not get random data from the TPM";
return false;
}
if (!platform_->WriteFile(FilePath(kDefaultEntropySourcePath), random)) {
LOG(ERROR) << "Error writing data to " << kDefaultEntropySourcePath;
return false;
}
return true;
}
void Service::UploadAlertsDataCallback() {
Tpm::AlertsData alerts;
if (tpm_) {
bool supported = tpm_->GetAlertsData(&alerts);
if (!supported) {
// success return code and unknown chip family means that chip does not
// support GetAlerts information. Return here as no need to reschedule
// the delayed task.
LOG(INFO) << "The TPM chip does not support GetAlertsData. "
<< "Stop UploadAlertsData task.";
return;
}
ReportAlertsData(alerts);
}
// We don't care about the parallel delay tasks number. Don't increase the
// parallel tasks count here.
mount_thread_.task_runner()->PostDelayedTask(
FROM_HERE,
base::Bind(&Service::UploadAlertsDataCallback, base::Unretained(this)),
base::TimeDelta::FromMilliseconds(upload_alerts_period_ms_));
}
bool Service::Reset() {
if (cryptohome_)
g_object_unref(cryptohome_);
cryptohome_ = reinterpret_cast<gobject::Cryptohome*>(
g_object_new(gobject::cryptohome_get_type(), NULL));
// Allow references to this instance.
cryptohome_->service = this;
if (loop_) {
::g_main_loop_unref(loop_);
}
loop_ = g_main_loop_new(NULL, false);
if (!loop_) {
LOG(ERROR) << "Failed to create main loop";
return false;
}
// Install the local event source for handling async results
event_source_.Reset(event_source_sink_, g_main_loop_get_context(loop_));
return true;
}
void Service::NotifyEvent(CryptohomeEventBase* event) {
if (!strcmp(event->GetEventName(), kMountTaskResultEventType)) {
MountTaskResult* result = static_cast<MountTaskResult*>(event);
scoped_refptr<UserSession> session =
GetUserSessionForMount(result->mount().get());
if (!result->return_data()) {
g_signal_emit(cryptohome_, async_complete_signal_, 0,
result->sequence_id(), result->return_status(),
result->return_code());
// TODO(wad) are there any non-mount uses of this type?
if (!result->return_status()) {
RemoveUserSession(session.get());
}
SendAsyncIdInfoToUma(result->sequence_id(), base::Time::Now());
} else {
brillo::glib::ScopedArray tmp_array(g_array_new(FALSE, FALSE, 1));
g_array_append_vals(tmp_array.get(), result->return_data()->data(),
result->return_data()->size());
g_signal_emit(cryptohome_, async_data_complete_signal_, 0,
result->sequence_id(), result->return_status(),
tmp_array.get());
brillo::SecureClear(tmp_array.get()->data, tmp_array.get()->len);
SendAsyncIdInfoToUma(result->sequence_id(), base::Time::Now());
}
if (result->pkcs11_init()) {
LOG(INFO) << "An asynchronous mount request with sequence id: "
<< result->sequence_id() << " finished; doing PKCS11 init...";
// We only report and init PKCS#11 for successful mounts.
if (result->return_status() && session.get()) {
InitializePkcs11(session.get());
}
} else if (result->guest()) {
if (!result->return_status()) {
DLOG(INFO) << "Dropping MountMap entry for failed Guest mount.";
RemoveUserSession(guest_user_);
}
}
} else if (!strcmp(event->GetEventName(), kTpmInitStatusEventType)) {
TpmInitStatus* result = static_cast<TpmInitStatus*>(event);
g_signal_emit(cryptohome_, tpm_init_signal_, 0, tpm_init_->IsTpmReady(),
tpm_init_->IsTpmEnabled(), result->get_took_ownership());
// TODO(wad) should we package up a InstallAttributes status here too?
} else if (!strcmp(event->GetEventName(), kDBusErrorReplyEventType)) {
DBusErrorReply* result = static_cast<DBusErrorReply*>(event);
result->Run();
} else if (!strcmp(event->GetEventName(), kDBusBlobReplyEventType)) {
DBusBlobReply* result = static_cast<DBusBlobReply*>(event);
result->Run();
} else if (!strcmp(event->GetEventName(), kDBusReplyEventType)) {
DBusReply* result = static_cast<DBusReply*>(event);
result->Run();
} else if (!strcmp(event->GetEventName(),
kDircryptoMigrationProgressEventType)) {
auto* progress = static_cast<DircryptoMigrationProgress*>(event);
g_signal_emit(cryptohome_, dircrypto_migration_progress_signal_,
0 /* signal detail (not used) */,
static_cast<int32_t>(progress->status()),
progress->current_bytes(), progress->total_bytes());
} else if (!strcmp(event->GetEventName(), kClosureEventType)) {
ClosureEvent* closure_event = static_cast<ClosureEvent*>(event);
closure_event->Run();
}
}
void Service::DoResetTPMContext(UserSession* session) {
if (session) {
crypto_->EnsureTpm(true);
}
}
void Service::OwnershipCallback(bool status, bool took_ownership) {
// Note that this function should only be called once during the lifetime of
// this process, extra calls will be dropped.
if (ownership_callback_has_run_) {
LOG(WARNING) << "Duplicated call to OwnershipCallback.";
return;
}
ownership_callback_has_run_ = true;
if (took_ownership) {
ReportTimerStop(kTpmTakeOwnershipTimer);
// Since ownership is already taken, we are not currently taking ownership.
tpm_init_->SetTpmBeingOwned(false);
// Let the |tpm_| object know about the ownership status
if (tpm_) {
tpm_->HandleOwnershipTakenEvent();
}
// When TPM initialization finishes, we need to tell every Mount to
// reinitialize its TPM context, since the TPM is now useable, and we might
// need to kick off their PKCS11 initialization if they were blocked before.
{
base::AutoLock _lock(sessions_lock_);
for (const auto& session_pair : sessions_) {
mount_thread_observer_.PostTask();
mount_thread_.task_runner()->PostTask(
FROM_HERE,
base::Bind(&Service::DoResetTPMContext, base::Unretained(this),
base::RetainedRef(session_pair.second)));
}
}
}
PostTask(FROM_HERE,
base::Bind(&Service::ConfigureOwnedTpm, base::Unretained(this),
status, took_ownership));
}
void Service::ConfigureOwnedTpm(bool status, bool took_ownership) {
LOG(INFO) << "Configuring TPM, ownership taken: " << took_ownership << ".";
if (took_ownership) {
// Check if we have pending pkcs11 init tasks due to tpm ownership
// not being done earlier. Trigger initialization if so.
{
base::AutoLock _lock(sessions_lock_);
for (const auto& session_pair : sessions_) {
UserSession* session = session_pair.second.get();
if (session->GetMount()->pkcs11_state() ==
cryptohome::Mount::kIsWaitingOnTPM) {
InitializePkcs11(session);
}
}
}
// Initialize the install-time locked attributes since we
// can't do it prior to ownership.
InitializeInstallAttributes();
}
event_source_.AddEvent(
std::make_unique<TpmInitStatus>(took_ownership, status));
// Do attestation work after AddEvent because it may take long.
AttestationInitializeTpmComplete();
// If we mounted before the TPM finished initialization, we must
// finalize the install attributes now too, otherwise it takes a
// full re-login cycle to finalize.
gboolean mounted = FALSE;
bool is_mounted = (IsMounted(&mounted, NULL) && mounted);
if (is_mounted && took_ownership &&
install_attrs_->status() == InstallAttributes::Status::kFirstInstall) {
scoped_refptr<UserSession> guest_session = GetUserSession(guest_user_);
bool guest_mounted =
guest_session.get() && guest_session->GetMount()->IsMounted();
if (!guest_mounted)
install_attrs_->Finalize();
}
}
void Service::CreateFingerprintManager() {
if (fingerprint_manager_.get() != nullptr) {
return;
}
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
scoped_refptr<dbus::Bus> bus(new dbus::Bus(options));
if (!bus->Connect()) {
LOG(ERROR) << "CreateFingerprintManager: Cannot connect to D-Bus.";
return;
}
fingerprint_manager_ = FingerprintManager::Create(
bus, dbus::ObjectPath(std::string(biod::kBiodServicePath)
.append(kCrosFpBiometricsManagerRelativePath)));
}
void Service::CompleteFingerprintCheckKeyEx(DBusGMethodInvocation* context,
FingerprintScanStatus status) {
BaseReply reply;
if (status == FingerprintScanStatus::FAILED_RETRY_ALLOWED)
reply.set_error(CRYPTOHOME_ERROR_FINGERPRINT_RETRY_REQUIRED);
if (status == FingerprintScanStatus::FAILED_RETRY_NOT_ALLOWED)
reply.set_error(CRYPTOHOME_ERROR_FINGERPRINT_DENIED);
SendReply(context, reply);
}
void Service::DoCheckKeyEx(std::unique_ptr<AccountIdentifier> identifier,
std::unique_ptr<AuthorizationRequest> authorization,
std::unique_ptr<CheckKeyRequest> check_key_request,
DBusGMethodInvocation* context) {
if (!identifier || !authorization || !check_key_request) {
SendInvalidArgsReply(context, "Failed to parse parameters.");
return;
}
if (GetAccountId(*identifier).empty()) {
SendInvalidArgsReply(context, "No email supplied");
return;
}
// Process challenge-response credentials asynchronously.
if (authorization->key().data().type() ==
KeyData::KEY_TYPE_CHALLENGE_RESPONSE) {
DoChallengeResponseCheckKeyEx(std::move(identifier),
std::move(authorization), context);
return;
}
if (authorization->key().data().type() == KeyData::KEY_TYPE_FINGERPRINT) {
const std::string obfuscated_username =
SanitizeUserNameWithSalt(GetAccountId(*identifier), system_salt_);
BaseReply reply;
if (!fingerprint_manager_) {
// Fingerprint manager failed to initialize, or the device may not
// support fingerprint auth at all.
reply.set_error(CRYPTOHOME_ERROR_FINGERPRINT_ERROR_INTERNAL);
SendReply(context, reply);
return;
}
if (!fingerprint_manager_->HasAuthSessionForUser(obfuscated_username)) {
reply.set_error(CRYPTOHOME_ERROR_FINGERPRINT_DENIED);
SendReply(context, reply);
return;
}
fingerprint_manager_->SetAuthScanDoneCallback(
base::Bind(&Service::CompleteFingerprintCheckKeyEx,
base::Unretained(this), context));
return;
}
// An AuthorizationRequest key without a label will test against
// all VaultKeysets of a compatible key().data().type().
if (authorization->key().secret().empty()) {
SendInvalidArgsReply(context, "No key secret supplied");
return;
}
Credentials credentials(GetAccountId(*identifier),
SecureBlob(authorization->key().secret().begin(),
authorization->key().secret().end()));
credentials.set_key_data(authorization->key().data());
const std::string obfuscated_username =
credentials.GetObfuscatedUsername(system_salt_);
BaseReply reply;
bool found_valid_credentials = false;
{
base::AutoLock _lock(sessions_lock_);
for (const auto& session_pair : sessions_) {
if (session_pair.second->VerifyCredentials(credentials)) {
found_valid_credentials = true;
break;
}
}
}
if (found_valid_credentials) {
// Entered the right creds, so reset LE credentials.
keyset_management_->ResetLECredentials(credentials);
SendReply(context, reply);
return;
}
// Fallthrough to HomeDirs to cover different keys for the same user.
if (homedirs_->Exists(obfuscated_username)) {
if (keyset_management_->AreCredentialsValid(credentials)) {
keyset_management_->ResetLECredentials(credentials);
} else {
// TODO(wad) Should this pass along KEY_NOT_FOUND too?
reply.set_error(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED);
ResetDictionaryAttackMitigation();
}
} else {
reply.set_error(CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND);
}
SendReply(context, reply);
}
gboolean Service::CheckKeyEx(GArray* account_id,
GArray* authorization_request,
GArray* check_key_request,
DBusGMethodInvocation* context) {
std::unique_ptr<AccountIdentifier> identifier(new AccountIdentifier);
std::unique_ptr<AuthorizationRequest> authorization(new AuthorizationRequest);
std::unique_ptr<CheckKeyRequest> request(new CheckKeyRequest);
// On parsing failure, pass along a NULL.
if (!identifier->ParseFromArray(account_id->data, account_id->len))
identifier.reset(NULL);
if (!authorization->ParseFromArray(authorization_request->data,
authorization_request->len))
authorization.reset(NULL);
if (!request->ParseFromArray(check_key_request->data, check_key_request->len))
request.reset(NULL);
// If PBs don't parse, the validation in the handler will catch it.
PostTask(FROM_HERE, base::Bind(&Service::DoCheckKeyEx, base::Unretained(this),
base::Passed(std::move(identifier)),
base::Passed(std::move(authorization)),
base::Passed(std::move(request)),
base::Unretained(context)));
return TRUE;
}
void Service::DoRemoveKeyEx(AccountIdentifier* identifier,
AuthorizationRequest* authorization,
RemoveKeyRequest* remove_key_request,
DBusGMethodInvocation* context) {
if (!identifier || !authorization || !remove_key_request) {
SendInvalidArgsReply(context, "Failed to parse parameters.");
return;
}
if (GetAccountId(*identifier).empty()) {
SendInvalidArgsReply(context, "No email supplied");
return;
}
// An AuthorizationRequest key without a label will test against
// all VaultKeysets of a compatible key().data().type().
if (authorization->key().secret().empty()) {
SendInvalidArgsReply(context, "No key secret supplied");
return;
}
if (remove_key_request->key().data().label().empty()) {
SendInvalidArgsReply(context, "No label provided for target key");
return;
}
BaseReply reply;
Credentials credentials(GetAccountId(*identifier),
SecureBlob(authorization->key().secret().begin(),
authorization->key().secret().end()));
credentials.set_key_data(authorization->key().data());
if (!homedirs_->Exists(credentials.GetObfuscatedUsername(system_salt_))) {
reply.set_error(CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND);
SendReply(context, reply);
return;
}
reply.set_error(keyset_management_->RemoveKeyset(
credentials, remove_key_request->key().data()));
if (reply.error() == CRYPTOHOME_ERROR_NOT_SET) {
// Don't set the error if there wasn't one.
reply.clear_error();
}
SendReply(context, reply);
}
gboolean Service::RemoveKeyEx(GArray* account_id,
GArray* authorization_request,
GArray* remove_key_request,
DBusGMethodInvocation* context) {
std::unique_ptr<AccountIdentifier> identifier(new AccountIdentifier);
std::unique_ptr<AuthorizationRequest> authorization(new AuthorizationRequest);
std::unique_ptr<RemoveKeyRequest> request(new RemoveKeyRequest);
// On parsing failure, pass along a NULL.
if (!identifier->ParseFromArray(account_id->data, account_id->len))
identifier.reset(NULL);
if (!authorization->ParseFromArray(authorization_request->data,
authorization_request->len))
authorization.reset(NULL);
if (!request->ParseFromArray(remove_key_request->data,
remove_key_request->len))
request.reset(NULL);
// If PBs don't parse, the validation in the handler will catch it.
PostTask(
FROM_HERE,
base::Bind(&Service::DoRemoveKeyEx, base::Unretained(this),
base::Owned(identifier.release()),
base::Owned(authorization.release()),
base::Owned(request.release()), base::Unretained(context)));
return TRUE;
}
void Service::DoMassRemoveKeys(AccountIdentifier* account_id,
AuthorizationRequest* authorization_request,
MassRemoveKeysRequest* mass_remove_keys_request,
DBusGMethodInvocation* context) {
if (!account_id || !authorization_request || !mass_remove_keys_request) {
SendInvalidArgsReply(context, "Failed to parse parameters.");
return;
}
const std::string username = GetAccountId(*account_id);
if (username.empty()) {
SendInvalidArgsReply(context, "No email supplied");
return;
}
if (authorization_request->key().secret().empty()) {
SendInvalidArgsReply(context, "No key secret supplied");
return;
}
BaseReply reply;
Credentials credentials(
username, SecureBlob(authorization_request->key().secret().begin(),
authorization_request->key().secret().end()));
credentials.set_key_data(authorization_request->key().data());
const std::string obfuscated_username =
SanitizeUserNameWithSalt(username, system_salt_);
if (!homedirs_->Exists(obfuscated_username)) {
reply.set_error(CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND);
SendReply(context, reply);
return;
}
if (!keyset_management_->AreCredentialsValid(credentials)) {
reply.set_error(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED);
SendReply(context, reply);
return;
}
// get all labels under the username
std::vector<std::string> labels;
if (!keyset_management_->GetVaultKeysetLabels(obfuscated_username, &labels)) {
reply.set_error(CRYPTOHOME_ERROR_KEY_NOT_FOUND);
SendReply(context, reply);
return;
}
// get all exempt labels from mass_remove_keys_request
std::unordered_set<std::string> exempt_labels;
for (int i = 0; i < mass_remove_keys_request->exempt_key_data_size(); i++) {
exempt_labels.insert(mass_remove_keys_request->exempt_key_data(i).label());
}
for (std::string label : labels) {
if (exempt_labels.find(label) == exempt_labels.end()) {
// non-exempt label, should be removed
std::unique_ptr<VaultKeyset> remove_vk(
keyset_management_->GetVaultKeyset(obfuscated_username, label));
if (!keyset_management_->ForceRemoveKeyset(obfuscated_username,
remove_vk->legacy_index())) {
LOG(ERROR) << "MassRemoveKeys: failed to remove keyset " << label;
reply.set_error(CRYPTOHOME_ERROR_BACKING_STORE_FAILURE);
SendReply(context, reply);
return;
}
}
}
SendReply(context, reply);
}
gboolean Service::MassRemoveKeys(GArray* account_id,
GArray* authorization_request,
GArray* mass_remove_keys_request,
DBusGMethodInvocation* context) {
auto identifier = std::make_unique<AccountIdentifier>();
auto authorization = std::make_unique<AuthorizationRequest>();
auto request = std::make_unique<MassRemoveKeysRequest>();
// On parsing failure, pass along a NULL.
if (!identifier->ParseFromArray(account_id->data, account_id->len))
identifier.reset(nullptr);
if (!authorization->ParseFromArray(authorization_request->data,
authorization_request->len))
authorization.reset(nullptr);
if (!request->ParseFromArray(mass_remove_keys_request->data,
mass_remove_keys_request->len))
request.reset(nullptr);
// If PBs don't parse, the validation in the handler will catch it.
PostTask(
FROM_HERE,
base::Bind(&Service::DoMassRemoveKeys, base::Unretained(this),
base::Owned(identifier.release()),
base::Owned(authorization.release()),
base::Owned(request.release()), base::Unretained(context)));
return TRUE;
}
void Service::DoListKeysEx(AccountIdentifier* identifier,
AuthorizationRequest* authorization,
ListKeysRequest* list_keys_request,
DBusGMethodInvocation* context) {
if (!identifier || !authorization || !list_keys_request) {
SendInvalidArgsReply(context, "Failed to parse parameters.");
return;
}
const std::string username = GetAccountId(*identifier);
if (username.empty()) {
SendInvalidArgsReply(context, "No email supplied");
return;
}
BaseReply reply;
const std::string obfuscated_username =
SanitizeUserNameWithSalt(username, system_salt_);
if (!homedirs_->Exists(obfuscated_username)) {
reply.set_error(CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND);
SendReply(context, reply);
return;
}
std::vector<std::string> labels;
if (!keyset_management_->GetVaultKeysetLabels(obfuscated_username, &labels)) {
reply.set_error(CRYPTOHOME_ERROR_KEY_NOT_FOUND);
}
ListKeysReply* list_keys_reply = reply.MutableExtension(ListKeysReply::reply);
for (const auto& label : labels)
list_keys_reply->add_labels(label);
SendReply(context, reply);
}
gboolean Service::ListKeysEx(GArray* account_id,
GArray* authorization_request,
GArray* list_keys_request,
DBusGMethodInvocation* context) {
std::unique_ptr<AccountIdentifier> identifier(new AccountIdentifier);
std::unique_ptr<AuthorizationRequest> authorization(new AuthorizationRequest);
std::unique_ptr<ListKeysRequest> request(new ListKeysRequest);
// On parsing failure, pass along a NULL.
if (!identifier->ParseFromArray(account_id->data, account_id->len))
identifier.reset(NULL);
if (!authorization->ParseFromArray(authorization_request->data,
authorization_request->len))
authorization.reset(NULL);
if (!request->ParseFromArray(list_keys_request->data, list_keys_request->len))
request.reset(NULL);
// If PBs don't parse, the validation in the handler will catch it.
PostTask(FROM_HERE, base::Bind(&Service::DoListKeysEx, base::Unretained(this),
base::Owned(identifier.release()),
base::Owned(authorization.release()),
base::Owned(request.release()),
base::Unretained(context)));
return TRUE;
}
void Service::DoGetKeyDataEx(AccountIdentifier* identifier,
AuthorizationRequest* authorization,
GetKeyDataRequest* get_key_data_request,
DBusGMethodInvocation* context) {
if (!identifier || !authorization || !get_key_data_request) {
SendInvalidArgsReply(context, "Failed to parse parameters.");
return;
}
if (GetAccountId(*identifier).empty()) {
SendInvalidArgsReply(context, "No email supplied");
return;
}
if (!get_key_data_request->has_key()) {
SendInvalidArgsReply(context, "No key attributes provided");
return;
}
BaseReply reply;
const std::string obfuscated_username =
SanitizeUserNameWithSalt(GetAccountId(*identifier), system_salt_);
if (!homedirs_->Exists(obfuscated_username)) {
reply.set_error(CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND);
SendReply(context, reply);
return;
}
// No error is thrown if there is no match.
reply.clear_error();
SendReply(context, reply);
}
gboolean Service::GetKeyDataEx(GArray* account_id,
GArray* authorization_request,
GArray* get_key_data_request,
DBusGMethodInvocation* context) {
std::unique_ptr<AccountIdentifier> identifier(new AccountIdentifier);
std::unique_ptr<AuthorizationRequest> authorization(new AuthorizationRequest);
std::unique_ptr<GetKeyDataRequest> request(new GetKeyDataRequest);
// On parsing failure, pass along a NULL.
if (!identifier->ParseFromArray(account_id->data, account_id->len)) {
identifier.reset(NULL);
}
if (!authorization->ParseFromArray(authorization_request->data,
authorization_request->len)) {
authorization.reset(NULL);
}
if (!request->ParseFromArray(get_key_data_request->data,
get_key_data_request->len)) {
request.reset(NULL);
}
// If PBs don't parse, the validation in the handler will catch it.
PostTask(
FROM_HERE,
base::Bind(&Service::DoGetKeyDataEx, base::Unretained(this),
base::Owned(identifier.release()),
base::Owned(authorization.release()),
base::Owned(request.release()), base::Unretained(context)));
return TRUE;
}
void Service::DoMigrateKeyEx(AccountIdentifier* account,
AuthorizationRequest* auth_request,
MigrateKeyRequest* migrate_request,
DBusGMethodInvocation* context) {
if (!account || !auth_request || !migrate_request) {
SendInvalidArgsReply(context, "Failed to parse parameters.");
return;
}
// Setup a reply to use during error handling.
BaseReply reply;
if (account->account_id().empty()) {
SendInvalidArgsReply(context, "Must supply account_id.");
return;
}
Credentials credentials(account->account_id(),
SecureBlob(migrate_request->secret()));
int key_index = -1;
if (!keyset_management_->Migrate(
credentials, SecureBlob(auth_request->key().secret()), &key_index)) {
reply.set_error(CRYPTOHOME_ERROR_MIGRATE_KEY_FAILED);
} else {
scoped_refptr<UserSession> session = GetUserSession(GetAccountId(*account));
if (session.get()) {
if (!session->SetCredentials(credentials, key_index)) {
LOG(WARNING) << "Failed to set new creds";
}
}
reply.clear_error();
}
SendReply(context, reply);
}
gboolean Service::MigrateKeyEx(GArray* account_ary,
GArray* auth_request_ary,
GArray* migrate_request_ary,
DBusGMethodInvocation* context) {
auto account = std::make_unique<AccountIdentifier>();
auto auth_request = std::make_unique<AuthorizationRequest>();
auto migrate_request = std::make_unique<MigrateKeyRequest>();
// On parsing failure, pass along a nullptr.
if (!account->ParseFromArray(account_ary->data, account_ary->len))
account.reset(nullptr);
if (!auth_request->ParseFromArray(auth_request_ary->data,
auth_request_ary->len)) {
auth_request.reset(nullptr);
}
if (!migrate_request->ParseFromArray(migrate_request_ary->data,
migrate_request_ary->len)) {
migrate_request.reset(nullptr);
}
// If PBs don't parse, the validation in the handler will catch it.
PostTask(FROM_HERE,
base::Bind(&Service::DoMigrateKeyEx, base::Unretained(this),
base::Owned(account.release()),
base::Owned(auth_request.release()),
base::Owned(migrate_request.release()),
base::Unretained(context)));
return TRUE;
}
void Service::DoAddKeyEx(AccountIdentifier* identifier,
AuthorizationRequest* authorization,
AddKeyRequest* add_key_request,
DBusGMethodInvocation* context) {
if (!identifier || !authorization || !add_key_request) {
SendInvalidArgsReply(context, "Failed to parse parameters.");
return;
}
// Setup a reply for use during error handling.
BaseReply reply;
if (GetAccountId(*identifier).empty()) {
SendInvalidArgsReply(context, "No email supplied");
return;
}
// An AuthorizationRequest key without a label will test against
// all VaultKeysets of a compatible key().data().type().
if (authorization->key().secret().empty()) {
SendInvalidArgsReply(context, "No key secret supplied");
return;
}
if (!add_key_request->has_key() || add_key_request->key().secret().empty()) {
SendInvalidArgsReply(context, "No new key supplied");
return;
}
if (add_key_request->key().data().label().empty()) {
SendInvalidArgsReply(context, "No new key label supplied");
return;
}
Credentials credentials(GetAccountId(*identifier),
SecureBlob(authorization->key().secret().begin(),
authorization->key().secret().end()));
credentials.set_key_data(authorization->key().data());
if (!homedirs_->Exists(credentials.GetObfuscatedUsername(system_salt_))) {
reply.set_error(CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND);
SendReply(context, reply);
return;
}
int index = -1;
SecureBlob new_secret(add_key_request->key().secret().begin(),
add_key_request->key().secret().end());
reply.set_error(keyset_management_->AddKeyset(
credentials, new_secret, &add_key_request->key().data(),
add_key_request->clobber_if_exists(), &index));
if (reply.error() == CRYPTOHOME_ERROR_NOT_SET) {
// Don't set the error if there wasn't one.
reply.clear_error();
}
SendReply(context, reply);
}
gboolean Service::AddKeyEx(GArray* account_id,
GArray* authorization_request,
GArray* add_key_request,
DBusGMethodInvocation* context) {
std::unique_ptr<AccountIdentifier> identifier(new AccountIdentifier);
std::unique_ptr<AuthorizationRequest> authorization(new AuthorizationRequest);
std::unique_ptr<AddKeyRequest> request(new AddKeyRequest);
// On parsing failure, pass along a NULL.
if (!identifier->ParseFromArray(account_id->data, account_id->len))
identifier.reset(NULL);
if (!authorization->ParseFromArray(authorization_request->data,
authorization_request->len))
authorization.reset(NULL);
if (!request->ParseFromArray(add_key_request->data, add_key_request->len))
request.reset(NULL);
// If PBs don't parse, the validation in the handler will catch it.
PostTask(FROM_HERE, base::Bind(&Service::DoAddKeyEx, base::Unretained(this),
base::Owned(identifier.release()),
base::Owned(authorization.release()),
base::Owned(request.release()),
base::Unretained(context)));
return TRUE;
}
void Service::DoAddDataRestoreKey(AccountIdentifier* identifier,
AuthorizationRequest* authorization,
DBusGMethodInvocation* context) {
if (!identifier || !authorization) {
SendInvalidArgsReply(context, "Failed to parse parameters.");
return;
}
if (GetAccountId(*identifier).empty()) {
SendInvalidArgsReply(context, "No email supplied");
return;
}
if (!authorization->has_key() || !authorization->key().has_secret()) {
SendInvalidArgsReply(context, "No key secret supplied");
return;
}
KeyData new_key_data;
BaseReply reply;
const auto data_restore_key =
CryptoLib::CreateSecureRandomBlob(kDefaultDataRestoreKeyLength);
new_key_data.set_label(kDataRestoreKeyLabel);
Credentials credentials(GetAccountId(*identifier),
SecureBlob(authorization->key().secret().begin(),
authorization->key().secret().end()));
credentials.set_key_data(authorization->key().data());
if (!homedirs_->Exists(credentials.GetObfuscatedUsername(system_salt_))) {
reply.set_error(CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND);
SendReply(context, reply);
return;
}
int index = -1;
reply.set_error(keyset_management_->AddKeyset(credentials, data_restore_key,
&new_key_data, true, &index));
if (reply.error() == CRYPTOHOME_ERROR_NOT_SET) {
// Don't set the error if there wasn't one.
reply.clear_error();
} else {
SendReply(context, reply);
return;
}
// send the raw bytes of data restore key as a part of reply back to caller
AddDataRestoreKeyReply* extension =
reply.MutableExtension(AddDataRestoreKeyReply::reply);
extension->set_data_restore_key(data_restore_key.to_string());
SendReply(context, reply);
}
gboolean Service::AddDataRestoreKey(GArray* account_id,
GArray* authorization_request,
DBusGMethodInvocation* context) {
auto identifier = std::make_unique<AccountIdentifier>();
auto authorization = std::make_unique<AuthorizationRequest>();
// On parsing failure, pass along a NULL.
if (!identifier->ParseFromArray(account_id->data, account_id->len))
identifier.reset(NULL);
if (!authorization->ParseFromArray(authorization_request->data,
authorization_request->len))
authorization.reset(NULL);
PostTask(FROM_HERE,
base::Bind(&Service::DoAddDataRestoreKey, base::Unretained(this),
base::Owned(identifier.release()),
base::Owned(authorization.release()),
base::Unretained(context)));
return TRUE;
}
void Service::DoRemoveEx(AccountIdentifier* identifier,
DBusGMethodInvocation* context) {
if (!identifier) {
SendInvalidArgsReply(context, "Failed to parse parameters.");
return;
}
if (GetAccountId(*identifier).empty()) {
SendInvalidArgsReply(context, "Empty account_id.");
return;
}
BaseReply reply;
if (!homedirs_->Remove(identifier->account_id()))
reply.set_error(CRYPTOHOME_ERROR_REMOVE_FAILED);
else
reply.clear_error();
SendReply(context, reply);
}
gboolean Service::RemoveEx(GArray* account_id, DBusGMethodInvocation* context) {
std::unique_ptr<AccountIdentifier> identifier(new AccountIdentifier);
// On parsing failure, pass along a NULL.
if (!identifier->ParseFromArray(account_id->data, account_id->len))
identifier.reset(NULL);
// If PBs don't parse, the validation in the handler will catch it.
PostTask(FROM_HERE, base::Bind(&Service::DoRemoveEx, base::Unretained(this),
base::Owned(identifier.release()),
base::Unretained(context)));
return TRUE;
}
gboolean Service::RenameCryptohome(const GArray* account_id_from,
const GArray* account_id_to,
DBusGMethodInvocation* response) {
std::unique_ptr<AccountIdentifier> id_from(new AccountIdentifier);
std::unique_ptr<AccountIdentifier> id_to(new AccountIdentifier);
if (!id_from->ParseFromArray(account_id_from->data, account_id_from->len)) {
id_from.reset(NULL);
}
if (!id_to->ParseFromArray(account_id_to->data, account_id_to->len)) {
id_to.reset(NULL);
}
// If PBs don't parse, the validation in the handler will catch it.
PostTask(
FROM_HERE,
base::Bind(&Service::DoRenameCryptohome, base::Unretained(this),
base::Owned(id_from.release()), base::Owned(id_to.release()),
base::Unretained(response)));
return TRUE;
}
void Service::DoRenameCryptohome(AccountIdentifier* id_from,
AccountIdentifier* id_to,
DBusGMethodInvocation* context) {
if (!id_from || !id_to) {
SendInvalidArgsReply(context, "Failed to parse parameters.");
return;
}
scoped_refptr<UserSession> session = GetUserSession(GetAccountId(*id_from));
const bool is_mounted = session.get() && session->GetMount()->IsMounted();
BaseReply reply;
if (is_mounted) {
LOG(ERROR) << "RenameCryptohome('" << GetAccountId(*id_from) << "','"
<< GetAccountId(*id_to)
<< "'): Unable to rename mounted cryptohome.";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY);
} else if (!homedirs_) {
LOG(ERROR) << "RenameCryptohome('" << GetAccountId(*id_from) << "','"
<< GetAccountId(*id_to) << "'): Homedirs not initialized.";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY);
} else if (!homedirs_->Rename(GetAccountId(*id_from), GetAccountId(*id_to))) {
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
}
SendReply(context, reply);
}
gboolean Service::GetAccountDiskUsage(const GArray* account_id,
DBusGMethodInvocation* response) {
std::unique_ptr<AccountIdentifier> identifier(new AccountIdentifier);
if (!identifier->ParseFromArray(account_id->data, account_id->len)) {
identifier.reset(NULL);
}
// If PBs don't parse, the validation in the handler will catch it.
PostTask(FROM_HERE,
base::Bind(&Service::DoGetAccountDiskUsage, base::Unretained(this),
base::Owned(identifier.release()),
base::Unretained(response)));
return TRUE;
}
void Service::DoGetAccountDiskUsage(AccountIdentifier* identifier,
DBusGMethodInvocation* context) {
if (!identifier) {
SendInvalidArgsReply(context, "Failed to parse parameters.");
return;
}
BaseReply reply;
reply.MutableExtension(GetAccountDiskUsageReply::reply)
->set_size(homedirs_->ComputeDiskUsage(GetAccountId(*identifier)));
SendReply(context, reply);
}
gboolean Service::GetSystemSalt(GArray** OUT_salt, GError** error) {
*OUT_salt = g_array_new(false, false, 1);
g_array_append_vals(*OUT_salt, system_salt_.data(), system_salt_.size());
return TRUE;
}
gboolean Service::GetSanitizedUsername(gchar* username,
gchar** OUT_sanitized,
GError** error) {
// Credentials::GetObfuscatedUsername() returns an uppercase hex encoding,
// while SanitizeUserName() returns a lowercase hex encoding. They should
// return the same value, but login_manager is already relying on
// SanitizeUserName() and that's the value that chrome should see.
std::string sanitized = brillo::cryptohome::home::SanitizeUserName(username);
if (sanitized.empty())
return FALSE;
*OUT_sanitized = g_strndup(sanitized.data(), sanitized.size());
return TRUE;
}
gboolean Service::IsMounted(gboolean* OUT_is_mounted, GError** error) {
// We consider "the cryptohome" to be mounted if any existing cryptohome is
// mounted.
*OUT_is_mounted = FALSE;
base::AutoLock _lock(sessions_lock_);
for (const auto& session_pair : sessions_) {
if (session_pair.second->GetMount()->IsMounted()) {
*OUT_is_mounted = TRUE;
break;
}
}
return TRUE;
}
gboolean Service::IsMountedForUser(gchar* userid,
gboolean* OUT_is_mounted,
gboolean* OUT_is_ephemeral_mount,
GError** error) {
scoped_refptr<UserSession> session = GetUserSession(userid);
*OUT_is_mounted = false;
*OUT_is_ephemeral_mount = false;
if (!session.get())
return TRUE;
if (session->GetMount()->IsNonEphemeralMounted()) {
*OUT_is_mounted = true;
*OUT_is_ephemeral_mount = false;
} else if (session->GetMount()->IsMounted()) {
*OUT_is_mounted = true;
*OUT_is_ephemeral_mount = true;
}
return TRUE;
}
void Service::DoUpdateTimestamp(scoped_refptr<UserSession> session) {
session->UpdateActivityTimestamp(0);
}
void Service::DoMount(scoped_refptr<UserSession> session,
const Credentials& credentials,
const Mount::MountArgs& mount_args,
base::WaitableEvent* event,
MountError* return_code,
bool* return_status) {
DCHECK(return_code);
// Remove all existing cryptohomes, except for the owner's one, if the
// ephemeral users policy is on.
// Note that a fresh policy value is read here, which in theory can conflict
// with the one used for calculation of |mount_args.is_ephemeral|. However,
// this inconsistency (whose probability is anyway pretty low in practice)
// should only lead to insignificant transient glitches, like an attempt to
// mount a non existing anymore cryptohome.
if (homedirs_->AreEphemeralUsersEnabled())
homedirs_->RemoveNonOwnerCryptohomes();
MountError code = AttemptUserMount(credentials, mount_args, session);
if (code == MOUNT_ERROR_TPM_COMM_ERROR) {
LOG(WARNING) << "TPM communication error. Retrying.";
code = AttemptUserMount(credentials, mount_args, session);
}
if (code == MOUNT_ERROR_VAULT_UNRECOVERABLE) {
LOG(ERROR) << "Unrecoverable vault, removing.";
if (!homedirs_->Remove(credentials.username())) {
LOG(ERROR) << "Failed to remove unrecoverable vault.";
code = MOUNT_ERROR_REMOVE_INVALID_USER_FAILED;
}
}
*return_code = code;
*return_status = (code == MOUNT_ERROR_NONE);
event->Signal();
}
gboolean Service::Mount(const gchar* userid,
const gchar* key,
gboolean create_if_missing,
gboolean ensure_ephemeral,
gint* OUT_error_code,
gboolean* OUT_result,
GError** error) {
CleanUpHiddenMounts();
// This is safe even if cryptohomed restarts during a multi-mount
// session and a new mount is added because cleanup is not forced.
// An existing process will keep the mount alive. On the next
// Unmount() it'll be forcibly cleaned up. In the case that
// cryptohomed crashes and misses the Unmount call, the stale
// mountpoints should still be cleaned up on the next daemon
// interaction.
//
// As we introduce multiple mounts, we can consider API changes to
// make it clearer what the UI expectations are (AddMount, etc).
if (sessions_.size() == 0)
// This could run on every interaction to catch any unused mounts.
CleanUpStaleMounts(false);
Credentials credentials(userid, SecureBlob(key, key + strlen(key)));
scoped_refptr<UserSession> guest_session = GetUserSession(guest_user_);
bool guest_mounted =
guest_session.get() && guest_session->GetMount()->IsMounted();
if (guest_mounted && !guest_session->Unmount()) {
LOG(ERROR) << "Could not unmount cryptohome from Guest session";
*OUT_error_code = MOUNT_ERROR_MOUNT_POINT_BUSY;
*OUT_result = FALSE;
return TRUE;
}
// Determine whether the mount should be ephemeral.
bool is_ephemeral = false;
MountError mount_error = MOUNT_ERROR_NONE;
if (!GetShouldMountAsEphemeral(userid, ensure_ephemeral, create_if_missing,
&is_ephemeral, &mount_error)) {
*OUT_error_code = mount_error;
*OUT_result = FALSE;
return TRUE;
}
// If a cryptohome is mounted for the user already, reuse that mount unless
// the |is_ephemeral| flag prevents it: When |is_ephemeral| is
// |true|, a cryptohome backed by tmpfs is required. If the currently
// mounted cryptohome is backed by a vault, it must be unmounted and
// remounted with a tmpfs backend.
scoped_refptr<UserSession> user_session = GetOrCreateUserSession(userid);
if (!user_session) {
LOG(ERROR) << "Could not initialize user session.";
*OUT_error_code = CRYPTOHOME_ERROR_MOUNT_FATAL;
*OUT_result = FALSE;
return TRUE;
}
if (is_ephemeral && user_session->GetMount()->IsNonEphemeralMounted()) {
// TODO(wad,ellyjones) Change this behavior to return failure even
// on a succesful unmount to tell chrome MOUNT_ERROR_NEEDS_RESTART.
if (!user_session->Unmount()) {
// The MountMap entry is kept since the Unmount failed.
LOG(ERROR) << "Could not unmount vault before an ephemeral mount.";
*OUT_error_code = MOUNT_ERROR_MOUNT_POINT_BUSY;
*OUT_result = FALSE;
return TRUE;
}
}
if (is_ephemeral && !create_if_missing) {
NOTREACHED() << "An ephemeral cryptohome can only be mounted when its "
"creation on-the-fly is allowed.";
*OUT_error_code = MOUNT_ERROR_INVALID_ARGS;
*OUT_result = FALSE;
return TRUE;
}
// TODO(wad) A case we haven't handled is mount-over of a non-ephemeral user.
// This is the case where there were 2 mount requests for a given user
// without any intervening unmount requests. This should only be able to
// happen if Chrome acts pathologically and re-requests a Mount. If,
// for instance, cryptohomed crashed, the MountMap would not contain the
// entry.
// TODO(wad) Can we get rid of this code path?
if (user_session->GetMount()->IsMounted()) {
// Count this event to confirm the code path can be removed.
ReportCrosEvent(kCryptohomeDoubleMount);
// TODO(wad) This tests against the stored credentials, not the TPM.
// If mounts are "repopulated", then a trip through the TPM would be needed.
LOG(INFO) << "Mount exists. Rechecking credentials.";
if (!user_session->VerifyCredentials(credentials)) {
// Need to take a trip through the TPM.
if (!keyset_management_->AreCredentialsValid(credentials)) {
LOG(ERROR) << "Failed to reauthenticate against the existing mount!";
// TODO(wad) Should we teardown all the mounts if this happens?
// RemoveAllMounts();
*OUT_error_code = MOUNT_ERROR_KEY_FAILURE;
*OUT_result = FALSE;
return TRUE;
}
}
// As far as PKCS#11 initialization goes, we treat this as a brand new
// mount request. InitializePkcs11() will detect and re-initialize if
// necessary except if the mount point is ephemeral as there is no PKCS#11
// data.
InitializePkcs11(user_session.get());
*OUT_error_code = MOUNT_ERROR_NONE;
*OUT_result = TRUE;
return TRUE;
}
// Any non-guest mount attempt triggers InstallAttributes finalization.
// The return value is ignored as it is possible we're pre-ownership.
// The next login will assure finalization if possible.
if (install_attrs_->status() == InstallAttributes::Status::kFirstInstall)
install_attrs_->Finalize();
Mount::MountArgs mount_args;
mount_args.create_if_missing = create_if_missing;
mount_args.is_ephemeral = is_ephemeral;
mount_args.create_as_ecryptfs = force_ecryptfs_;
// TODO(kinaba): Currently Mount is not used for type of accounts that
// we need to force dircrypto. Add an option when it becomes necessary.
mount_args.force_dircrypto = false;
MountError return_code = MOUNT_ERROR_NONE;
bool return_status = false;
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
PostTask(FROM_HERE,
base::Bind(&Service::DoMount, base::Unretained(this),
base::RetainedRef(user_session), std::cref(credentials),
std::cref(mount_args), base::Unretained(&event),
base::Unretained(&return_code),
base::Unretained(&return_status)));
event.Wait();
// Update the timestamp for old user detection in the background.
PostTask(FROM_HERE,
base::Bind(&Service::DoUpdateTimestamp, base::Unretained(this),
base::RetainedRef(user_session)));
user_session->GetMount()->set_pkcs11_state(cryptohome::Mount::kUninitialized);
if (return_status) {
InitializePkcs11(user_session.get());
} else {
RemoveUserSession(user_session.get());
}
*OUT_error_code = return_code;
*OUT_result = return_status;
return TRUE;
}
void Service::DoMountEx(std::unique_ptr<AccountIdentifier> identifier,
std::unique_ptr<AuthorizationRequest> authorization,
std::unique_ptr<MountRequest> request,
DBusGMethodInvocation* context) {
if (!identifier || !authorization || !request) {
SendInvalidArgsReply(context, "Failed to parse parameters.");
return;
}
// Setup a reply for use during error handling.
BaseReply reply;
// Needed to pass along |recreated|
MountReply* mount_reply = reply.MutableExtension(MountReply::reply);
mount_reply->set_recreated(false);
// At present, we only enforce non-empty email addresses.
// In the future, we may wish to canonicalize if we don't move
// to requiring a IdP-unique identifier.
const std::string& account_id = GetAccountId(*identifier);
if (account_id.empty()) {
SendInvalidArgsReply(context, "No email supplied");
return;
}
if (request->public_mount()) {
std::string public_mount_passkey;
if (!GetPublicMountPassKey(account_id, &public_mount_passkey)) {
LOG(ERROR) << "Could not get public mount passkey.";
reply.set_error(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED);
SendReply(context, reply);
return;
}
// Set the secret as the key for cryptohome authorization/creation.
authorization->mutable_key()->set_secret(public_mount_passkey);
if (request->has_create()) {
request->mutable_create()->mutable_keys(0)->set_secret(
public_mount_passkey);
}
}
// An AuthorizationRequest key without a label will test against
// all VaultKeysets of a compatible key().data().type().
if (authorization->key().secret().empty() &&
authorization->key().data().type() !=
KeyData::KEY_TYPE_CHALLENGE_RESPONSE) {
SendInvalidArgsReply(context, "No key secret supplied");
return;
}
if (request->has_create()) {
if (request->create().copy_authorization_key()) {
Key* auth_key = request->mutable_create()->add_keys();
*auth_key = authorization->key();
}
int keys_size = request->create().keys_size();
if (keys_size == 0) {
SendInvalidArgsReply(context, "CreateRequest supplied with no keys");
return;
} else if (keys_size > 1) {
LOG(ERROR) << "MountEx: unimplemented CreateRequest with multiple keys";
reply.set_error(CRYPTOHOME_ERROR_NOT_IMPLEMENTED);
SendReply(context, reply);
return;
} else {
const Key key = request->create().keys(0);
// TODO(wad) Ensure the labels are all unique.
if (!key.has_data() || key.data().label().empty() ||
(key.secret().empty() &&
key.data().type() != KeyData::KEY_TYPE_CHALLENGE_RESPONSE)) {
SendInvalidArgsReply(context,
"CreateRequest Keys are not fully specified");
return;
}
}
}
// Determine whether the mount should be ephemeral.
bool is_ephemeral = false;
MountError mount_error = MOUNT_ERROR_NONE;
if (!GetShouldMountAsEphemeral(account_id, request->require_ephemeral(),
request->has_create(), &is_ephemeral,
&mount_error)) {
reply.set_error(MountErrorToCryptohomeError(mount_error));
SendReply(context, reply);
return;
}
Mount::MountArgs mount_args;
mount_args.create_if_missing = request->has_create();
mount_args.is_ephemeral = is_ephemeral;
mount_args.create_as_ecryptfs =
force_ecryptfs_ ||
(request->has_create() && request->create().force_ecryptfs());
mount_args.to_migrate_from_ecryptfs = request->to_migrate_from_ecryptfs();
// Force_ecryptfs_ wins.
mount_args.force_dircrypto =
!force_ecryptfs_ && request->force_dircrypto_if_available();
mount_args.shadow_only = request->hidden_mount();
// Process challenge-response credentials asynchronously.
if (authorization->key().data().type() ==
KeyData::KEY_TYPE_CHALLENGE_RESPONSE) {
DoChallengeResponseMountEx(std::move(identifier), std::move(authorization),
std::move(request), mount_args, context);
return;
}
auto credentials = std::make_unique<Credentials>(
account_id, SecureBlob(authorization->key().secret().begin(),
authorization->key().secret().end()));
// Everything else can be the default.
credentials->set_key_data(authorization->key().data());
ContinueMountExWithCredentials(std::move(identifier),
std::move(authorization), std::move(request),
std::move(credentials), mount_args, context);
LOG(INFO) << "Finished mount request process";
}
bool Service::InitForChallengeResponseAuth(CryptohomeErrorCode* error_code) {
if (challenge_credentials_helper_) {
// Already successfully initialized.
return true;
}
if (!tpm_) {
LOG(ERROR) << "Cannot do challenge-response authentication without TPM";
*error_code = CRYPTOHOME_ERROR_MOUNT_FATAL;
return false;
}
if (!tpm_init_->IsTpmReady()) {
LOG(ERROR) << "TPM must be initialized in order to do challenge-response "
"authentication";
*error_code = CRYPTOHOME_ERROR_MOUNT_FATAL;
return false;
}
// Fail if the TPM is known to be vulnerable and we're not in a test image.
const base::Optional<bool> is_srk_roca_vulnerable =
tpm_->IsSrkRocaVulnerable();
if (!is_srk_roca_vulnerable.has_value()) {
LOG(ERROR) << "Cannot do challenge-response mount: Failed to check for "
"ROCA vulnerability";
*error_code = CRYPTOHOME_ERROR_MOUNT_FATAL;
return false;
}
if (is_srk_roca_vulnerable.value()) {
if (!IsOsTestImage()) {
LOG(ERROR)
<< "Cannot do challenge-response mount: TPM is ROCA vulnerable";
*error_code = CRYPTOHOME_ERROR_TPM_UPDATE_REQUIRED;
return false;
}
LOG(WARNING) << "TPM is ROCA vulnerable; ignoring this for "
"challenge-response mount due to running in test image";
}
// Lazily create the helper object that manages generation/decryption of
// credentials for challenge-protected vaults.
Blob delegate_blob, delegate_secret;
bool has_reset_lock_permissions = false;
if (!AttestationGetDelegateCredentials(&delegate_blob, &delegate_secret,
&has_reset_lock_permissions)) {
LOG(ERROR)
<< "Cannot do challenge-response authentication without TPM delegate";
*error_code = CRYPTOHOME_ERROR_MOUNT_FATAL;
return false;
}
default_challenge_credentials_helper_ =
std::make_unique<ChallengeCredentialsHelperImpl>(tpm_, delegate_blob,
delegate_secret);
challenge_credentials_helper_ = default_challenge_credentials_helper_.get();
return true;
}
void Service::DoChallengeResponseCheckKeyEx(
std::unique_ptr<AccountIdentifier> identifier,
std::unique_ptr<AuthorizationRequest> authorization,
DBusGMethodInvocation* context) {
DCHECK_EQ(authorization->key().data().type(),
KeyData::KEY_TYPE_CHALLENGE_RESPONSE);
BaseReply reply;
CryptohomeErrorCode error_code = CRYPTOHOME_ERROR_NOT_SET;
if (!InitForChallengeResponseAuth(&error_code)) {
reply.set_error(error_code);
SendReply(context, reply);
return;
}
if (!authorization->has_key_delegate() ||
!authorization->key_delegate().has_dbus_service_name()) {
LOG(ERROR) << "Cannot do challenge-response authentication without key "
"delegate information";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
SendReply(context, reply);
return;
}
if (!authorization->key().data().challenge_response_key_size()) {
LOG(ERROR) << "Missing challenge-response key information";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
SendReply(context, reply);
return;
}
if (authorization->key().data().challenge_response_key_size() > 1) {
LOG(ERROR)
<< "Using multiple challenge-response keys at once is unsupported";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
SendReply(context, reply);
return;
}
// Begin from attempting a lightweight check that doesn't use the vault keyset
// or heavy TPM operations, and therefore is faster than the full check and
// also works in case the mount is ephemeral.
TryLightweightChallengeResponseCheckKeyEx(std::move(identifier),
std::move(authorization), context);
}
void Service::TryLightweightChallengeResponseCheckKeyEx(
std::unique_ptr<AccountIdentifier> identifier,
std::unique_ptr<AuthorizationRequest> authorization,
DBusGMethodInvocation* context) {
DCHECK_EQ(authorization->key().data().type(),
KeyData::KEY_TYPE_CHALLENGE_RESPONSE);
DCHECK(challenge_credentials_helper_);
const std::string& account_id = GetAccountId(*identifier);
const std::string obfuscated_username =
SanitizeUserNameWithSalt(account_id, system_salt_);
std::unique_ptr<KeyChallengeService> key_challenge_service =
key_challenge_service_factory_->New(
system_dbus_connection_.Connect(),
authorization->key_delegate().dbus_service_name());
if (!key_challenge_service) {
LOG(ERROR) << "Failed to create key challenge service";
BaseReply reply;
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
SendReply(context, reply);
return;
}
base::Optional<KeyData> found_session_key_data;
{
base::AutoLock lock(sessions_lock_);
for (const auto& session_pair : sessions_) {
const scoped_refptr<UserSession>& session = session_pair.second;
if (session->VerifyUser(obfuscated_username) &&
KeyMatchesForLightweightChallengeResponseCheck(
authorization->key().data(), *session)) {
found_session_key_data = session->key_data();
break;
}
}
}
if (!found_session_key_data) {
// No matching user session found, so fall back to the full check.
OnLightweightChallengeResponseCheckKeyExDone(std::move(identifier),
std::move(authorization),
context, /*success=*/false);
return;
}
// Attempt the lightweight check against the found user session.
challenge_credentials_helper_->VerifyKey(
account_id, *found_session_key_data, std::move(key_challenge_service),
base::BindOnce(&Service::OnLightweightChallengeResponseCheckKeyExDone,
base::Unretained(this), std::move(identifier),
std::move(authorization), base::Unretained(context)));
}
void Service::OnLightweightChallengeResponseCheckKeyExDone(
std::unique_ptr<AccountIdentifier> identifier,
std::unique_ptr<AuthorizationRequest> authorization,
DBusGMethodInvocation* context,
bool success) {
if (!success) {
DoFullChallengeResponseCheckKeyEx(std::move(identifier),
std::move(authorization), context);
return;
}
// Note that the LE credentials are not reset here, since we don't have the
// full credentials after the lightweight check.
SendReply(context, BaseReply());
}
void Service::DoFullChallengeResponseCheckKeyEx(
std::unique_ptr<AccountIdentifier> identifier,
std::unique_ptr<AuthorizationRequest> authorization,
DBusGMethodInvocation* context) {
DCHECK_EQ(authorization->key().data().type(),
KeyData::KEY_TYPE_CHALLENGE_RESPONSE);
DCHECK(challenge_credentials_helper_);
const std::string& account_id = GetAccountId(*identifier);
const std::string obfuscated_username =
SanitizeUserNameWithSalt(account_id, system_salt_);
BaseReply reply;
std::unique_ptr<KeyChallengeService> key_challenge_service =
key_challenge_service_factory_->New(
system_dbus_connection_.Connect(),
authorization->key_delegate().dbus_service_name());
if (!key_challenge_service) {
LOG(ERROR) << "Failed to create key challenge service";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
SendReply(context, reply);
return;
}
if (!homedirs_->Exists(obfuscated_username)) {
reply.set_error(CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND);
SendReply(context, reply);
return;
}
std::unique_ptr<VaultKeyset> vault_keyset(keyset_management_->GetVaultKeyset(
obfuscated_username, authorization->key().data().label()));
if (!vault_keyset) {
LOG(ERROR) << "No existing challenge-response vault keyset found";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
SendReply(context, reply);
return;
}
challenge_credentials_helper_->Decrypt(
account_id, authorization->key().data(),
vault_keyset->serialized().signature_challenge_info(),
std::move(key_challenge_service),
base::Bind(&Service::OnFullChallengeResponseCheckKeyExDone,
base::Unretained(this), base::Unretained(context)));
}
void Service::OnFullChallengeResponseCheckKeyExDone(
DBusGMethodInvocation* context, std::unique_ptr<Credentials> credentials) {
if (!credentials) {
LOG(ERROR) << "Key checking failed due to failure to obtain "
"challenge-response credentials";
BaseReply reply;
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
SendReply(context, reply);
return;
}
// Entered the right creds, so reset LE credentials.
keyset_management_->ResetLECredentials(*credentials);
SendReply(context, BaseReply());
}
void Service::DoChallengeResponseMountEx(
std::unique_ptr<AccountIdentifier> identifier,
std::unique_ptr<AuthorizationRequest> authorization,
std::unique_ptr<MountRequest> request,
const Mount::MountArgs& mount_args,
DBusGMethodInvocation* context) {
DCHECK_EQ(authorization->key().data().type(),
KeyData::KEY_TYPE_CHALLENGE_RESPONSE);
// Setup a reply for use during error handling.
BaseReply reply;
reply.MutableExtension(MountReply::reply)->set_recreated(false);
CryptohomeErrorCode error_code = CRYPTOHOME_ERROR_NOT_SET;
if (!InitForChallengeResponseAuth(&error_code)) {
reply.set_error(error_code);
SendReply(context, reply);
return;
}
const std::string& account_id = GetAccountId(*identifier);
const std::string obfuscated_username =
SanitizeUserNameWithSalt(account_id, system_salt_);
const KeyData key_data = authorization->key().data();
if (!authorization->has_key_delegate() ||
!authorization->key_delegate().has_dbus_service_name()) {
LOG(ERROR) << "Cannot do challenge-response mount without key delegate "
"information";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
SendReply(context, reply);
return;
}
std::unique_ptr<KeyChallengeService> key_challenge_service =
key_challenge_service_factory_->New(
system_dbus_connection_.Connect(),
authorization->key_delegate().dbus_service_name());
if (!key_challenge_service) {
LOG(ERROR) << "Failed to create key challenge service";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
SendReply(context, reply);
return;
}
if (!homedirs_->Exists(obfuscated_username) &&
!mount_args.create_if_missing) {
LOG(ERROR) << "Cannot do challenge-response mount. Account not found.";
reply.set_error(CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND);
SendReply(context, reply);
return;
}
std::unique_ptr<VaultKeyset> vault_keyset(keyset_management_->GetVaultKeyset(
obfuscated_username, authorization->key().data().label()));
const bool use_existing_credentials =
vault_keyset && !mount_args.is_ephemeral;
if (use_existing_credentials) {
challenge_credentials_helper_->Decrypt(
account_id, key_data,
vault_keyset->serialized().signature_challenge_info(),
std::move(key_challenge_service),
base::BindOnce(&Service::OnChallengeResponseMountCredentialsObtained,
base::Unretained(this),
base::Passed(std::move(identifier)),
base::Passed(std::move(authorization)),
base::Passed(std::move(request)), mount_args,
base::Unretained(context)));
} else {
if (!mount_args.create_if_missing) {
LOG(ERROR) << "No existing challenge-response vault keyset found";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
SendReply(context, reply);
return;
}
std::vector<std::map<uint32_t, Blob>> pcr_restrictions;
GetChallengeCredentialsPcrRestrictions(obfuscated_username,
&pcr_restrictions);
challenge_credentials_helper_->GenerateNew(
account_id, key_data, pcr_restrictions,
std::move(key_challenge_service),
base::BindOnce(&Service::OnChallengeResponseMountCredentialsObtained,
base::Unretained(this),
base::Passed(std::move(identifier)),
base::Passed(std::move(authorization)),
base::Passed(std::move(request)), mount_args,
base::Unretained(context)));
}
}
void Service::OnChallengeResponseMountCredentialsObtained(
std::unique_ptr<AccountIdentifier> identifier,
std::unique_ptr<AuthorizationRequest> authorization,
std::unique_ptr<MountRequest> request,
const Mount::MountArgs& mount_args,
DBusGMethodInvocation* context,
std::unique_ptr<Credentials> credentials) {
DCHECK_EQ(authorization->key().data().type(),
KeyData::KEY_TYPE_CHALLENGE_RESPONSE);
if (!credentials) {
LOG(ERROR) << "Could not mount due to failure to obtain challenge-response "
"credentials";
BaseReply reply;
reply.MutableExtension(MountReply::reply)->set_recreated(false);
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
SendReply(context, reply);
return;
}
DCHECK_EQ(credentials->key_data().type(),
KeyData::KEY_TYPE_CHALLENGE_RESPONSE);
ContinueMountExWithCredentials(std::move(identifier),
std::move(authorization), std::move(request),
std::move(credentials), mount_args, context);
}
void Service::ContinueMountExWithCredentials(
std::unique_ptr<AccountIdentifier> identifier,
std::unique_ptr<AuthorizationRequest> authorization,
std::unique_ptr<MountRequest> request,
std::unique_ptr<Credentials> credentials,
const Mount::MountArgs& mount_args,
DBusGMethodInvocation* context) {
if (!CleanUpHiddenMounts()) {
LOG(WARNING) << "Failed to clean up hidden mounts";
}
// Setup a reply for use during error handling.
BaseReply reply;
// Needed to pass along |recreated|
MountReply* mount_reply = reply.MutableExtension(MountReply::reply);
mount_reply->set_recreated(false);
// See ::Mount for detailed commentary.
bool other_sessions_active = true;
if (sessions_.size() == 0)
other_sessions_active = CleanUpStaleMounts(false);
if (!request->has_create() &&
!homedirs_->Exists(credentials->GetObfuscatedUsername(system_salt_))) {
LOG(ERROR) << "Account not found when mounting with credentials.";
reply.set_error(CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND);
SendReply(context, reply);
return;
}
// Provide an authoritative filesystem-sanitized username.
mount_reply->set_sanitized_username(
brillo::cryptohome::home::SanitizeUserName(GetAccountId(*identifier)));
// While it would be cleaner to implement the privilege enforcement
// here, that can only be done if a label was supplied. If a wildcard
// was supplied, then we can only perform the enforcement after the
// matching key is identified.
//
// See Mount::MountCryptohome for privilege checking.
scoped_refptr<UserSession> guest_session = GetUserSession(guest_user_);
bool guest_mounted =
guest_session.get() && guest_session->GetMount()->IsMounted();
// TODO(wad,ellyjones) Change this behavior to return failure even
// on a succesful unmount to tell chrome MOUNT_ERROR_NEEDS_RESTART.
if (guest_mounted && !guest_session->Unmount()) {
LOG(ERROR) << "Could not unmount cryptohome from Guest session";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY);
SendReply(context, reply);
return;
}
scoped_refptr<UserSession> user_session =
GetOrCreateUserSession(GetAccountId(*identifier));
if (!user_session) {
LOG(ERROR) << "Could not initialize user session.";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
SendReply(context, reply);
return;
}
if (request->hidden_mount() && user_session->GetMount()->IsMounted()) {
LOG(ERROR) << "Hidden mount requested, but mount already exists.";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY);
SendReply(context, reply);
return;
}
// For public mount, don't proceed if there is any existing mount or stale
// mount. Exceptionally, it is normal and ok to have a failed previous mount
// attempt for the same user.
const bool only_self_unmounted_attempt =
sessions_.size() == 1 && !user_session->GetMount()->IsMounted();
if (request->public_mount() && other_sessions_active &&
!only_self_unmounted_attempt) {
LOG(ERROR) << "Public mount requested with other sessions active.";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY);
SendReply(context, reply);
return;
}
// Don't overlay an ephemeral mount over a file-backed one.
if (mount_args.is_ephemeral &&
user_session->GetMount()->IsNonEphemeralMounted()) {
// TODO(wad,ellyjones) Change this behavior to return failure even
// on a succesful unmount to tell chrome MOUNT_ERROR_NEEDS_RESTART.
if (!user_session->Unmount()) {
LOG(ERROR) << "Could not unmount vault before an ephemeral mount.";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY);
SendReply(context, reply);
return;
}
}
if (mount_args.is_ephemeral && !mount_args.create_if_missing) {
LOG(ERROR) << "An ephemeral cryptohome can only be mounted when its "
"creation on-the-fly is allowed.";
reply.set_error(CRYPTOHOME_ERROR_INVALID_ARGUMENT);
SendReply(context, reply);
return;
}
if (user_session->GetMount()->IsMounted()) {
// Attempt a short-circuited credential test.
if (user_session->VerifyCredentials(*credentials)) {
SendReply(context, reply);
keyset_management_->ResetLECredentials(*credentials);
return;
}
// If the Mount has invalid credentials (repopulated from system state)
// this will ensure a user can still sign-in with the right ones.
// TODO(wad) Should we unmount on a failed re-mount attempt?
if (!user_session->VerifyCredentials(*credentials) &&
!keyset_management_->AreCredentialsValid(*credentials)) {
LOG(ERROR) << "Credentials are invalid";
reply.set_error(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED);
} else {
keyset_management_->ResetLECredentials(*credentials);
}
SendReply(context, reply);
return;
}
// See Mount for a relevant comment.
if (install_attrs_->status() == InstallAttributes::Status::kFirstInstall) {
install_attrs_->Finalize();
}
// As per the other timers, this really only tracks time spent in
// MountCryptohome() not in the other areas prior.
ReportTimerStart(kMountExTimer);
// Remove all existing cryptohomes, except for the owner's one, if the
// ephemeral users policy is on.
// Note that a fresh policy value is read here, which in theory can conflict
// with the one used for calculation of |mount_args.is_ephemeral|. However,
// this inconsistency (whose probability is anyway pretty low in practice)
// should only lead to insignificant transient glitches, like an attempt to
// mount a non existing anymore cryptohome.
if (homedirs_->AreEphemeralUsersEnabled())
homedirs_->RemoveNonOwnerCryptohomes();
// Do actual mounting here.
MountError code = AttemptUserMount(*credentials, mount_args, user_session);
if (code == MOUNT_ERROR_TPM_COMM_ERROR) {
LOG(WARNING) << "TPM communication error. Retrying.";
code = AttemptUserMount(*credentials, mount_args, user_session);
}
if (code == MOUNT_ERROR_VAULT_UNRECOVERABLE) {
LOG(ERROR) << "Unrecoverable vault, removing";
if (!homedirs_->Remove(credentials->username())) {
LOG(ERROR) << "Failed to remove unrecoverable vault";
code = MOUNT_ERROR_REMOVE_INVALID_USER_FAILED;
}
}
// PKCS#11 always starts out uninitialized right after a fresh mount.
user_session->GetMount()->set_pkcs11_state(cryptohome::Mount::kUninitialized);
// Mark the timer as done.
ReportTimerStop(kMountExTimer);
if (code != MOUNT_ERROR_NONE) {
LOG(ERROR) << "Failed to mount cryptohome, error = " << code;
reply.set_error(MountErrorToCryptohomeError(code));
ResetDictionaryAttackMitigation();
SendReply(context, reply);
return;
}
keyset_management_->ResetLECredentials(*credentials);
SendReply(context, reply);
if (!request->hidden_mount()) {
// Time to push the task for PKCS#11 initialization.
// TODO(wad) This call will PostTask back to the same thread. It is safe,
// but it seems pointless.
InitializePkcs11(user_session.get());
}
}
void Service::GetChallengeCredentialsPcrRestrictions(
const std::string& obfuscated_username,
std::vector<std::map<uint32_t, Blob>>* pcr_restrictions) {
{
std::map<uint32_t, Blob> pcrs_1;
for (const auto& pcr :
tpm_->GetPcrMap(obfuscated_username, false /* use_extended_pcr */)) {
pcrs_1[pcr.first] = BlobFromString(pcr.second);
}
pcr_restrictions->push_back(pcrs_1);
}
{
std::map<uint32_t, Blob> pcrs_2;
for (const auto& pcr :
tpm_->GetPcrMap(obfuscated_username, true /* use_extended_pcr */)) {
pcrs_2[pcr.first] = BlobFromString(pcr.second);
}
pcr_restrictions->push_back(pcrs_2);
}
}
gboolean Service::MountEx(const GArray* account_id,
const GArray* authorization_request,
const GArray* mount_request,
DBusGMethodInvocation* context) {
LOG(INFO) << "Received a mount request.";
std::unique_ptr<AccountIdentifier> identifier(new AccountIdentifier);
std::unique_ptr<AuthorizationRequest> authorization(new AuthorizationRequest);
std::unique_ptr<MountRequest> request(new MountRequest);
// On parsing failure, pass along a NULL.
if (!identifier->ParseFromArray(account_id->data, account_id->len))
identifier.reset(NULL);
if (!authorization->ParseFromArray(authorization_request->data,
authorization_request->len))
authorization.reset(NULL);
if (!request->ParseFromArray(mount_request->data, mount_request->len))
request.reset(NULL);
// If PBs don't parse, the validation in the handler will catch it.
PostTask(FROM_HERE, base::Bind(&Service::DoMountEx, base::Unretained(this),
base::Passed(std::move(identifier)),
base::Passed(std::move(authorization)),
base::Passed(std::move(request)),
base::Unretained(context)));
return TRUE;
}
void Service::SendDircryptoMigrationProgressSignal(
DircryptoMigrationStatus status,
uint64_t current_bytes,
uint64_t total_bytes) {
event_source_.AddEvent(std::make_unique<DircryptoMigrationProgress>(
status, current_bytes, total_bytes));
}
void Service::DoMountGuestEx(scoped_refptr<UserSession> guest_session,
std::unique_ptr<MountGuestRequest> request_pb,
DBusGMethodInvocation* context) {
if (!request_pb) {
SendInvalidArgsReply(context, "Bad MountGuestRequest");
return;
}
BaseReply reply;
// As per the other timers, this really only tracks time spent in
// MountGuestCryptohome() not in the other areas prior.
ReportTimerStart(kMountGuestExTimer);
MountError code = guest_session->MountGuest();
// Mark the timer as done.
ReportTimerStop(kMountGuestExTimer);
if (code != MOUNT_ERROR_NONE)
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
else
reply.clear_error();
SendReply(context, reply);
}
gboolean Service::MountGuestEx(GArray* request,
DBusGMethodInvocation* context) {
auto request_pb = std::make_unique<MountGuestRequest>();
if (!request_pb->ParseFromArray(request->data, request->len))
request_pb.reset(nullptr);
if (sessions_.size() != 0)
LOG(WARNING) << "Guest mount requested with other sessions active.";
// Rather than make it safe to check the size, then clean up, just always
// clean up.
bool ok = RemoveAllMounts(true);
// Create a ref-counted guest mount for async use and then throw it away.
scoped_refptr<UserSession> guest_session =
GetOrCreateUserSession(guest_user_);
BaseReply reply;
if (!ok) {
LOG(ERROR) << "Could not unmount cryptohomes for Guest use";
if (!RemoveUserSession(guest_user_)) {
LOG(ERROR) << "Unexpectedly cannot drop unused Guest mount from map.";
}
reply.set_error(CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY);
return TRUE;
}
if (!guest_session) {
LOG(ERROR) << "Could not initialize guest mount.";
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
return TRUE;
}
PostTask(FROM_HERE,
base::Bind(&Service::DoMountGuestEx, base::Unretained(this),
guest_session, base::Passed(std::move(request_pb)),
base::Unretained(context)));
return TRUE;
}
// Unmount all mounted cryptohomes.
gboolean Service::Unmount(gboolean* OUT_result, GError** error) {
*OUT_result = RemoveAllMounts(true);
// If there are any unexpected mounts lingering from a crash/restart,
// clean them up now.
CleanUpStaleMounts(true);
return TRUE;
}
void Service::DoUnmountEx(std::unique_ptr<UnmountRequest> request_pb,
DBusGMethodInvocation* context) {
if (!request_pb) {
SendInvalidArgsReply(context, "Bad UnmountRequest");
return;
}
BaseReply reply;
if (!RemoveAllMounts(true))
reply.set_error(CRYPTOHOME_ERROR_MOUNT_FATAL);
else
reply.clear_error();
// If there are any unexpected mounts lingering from a crash/restart,
// clean them up now.
CleanUpStaleMounts(true);
SendReply(context, reply);
// TODO(chromium:1109147): Remove this INFO log after we solve this issue.
LOG(INFO) << "Finished unmount request process";
}
gboolean Service::UnmountEx(GArray* request, DBusGMethodInvocation* context) {
// TODO(chromium:1109147): Remove this INFO log after we solve this issue.
LOG(INFO) << "Received an unmount request.";
auto request_pb = std::make_unique<UnmountRequest>();
if (!request_pb->ParseFromArray(request->data, request->len))
request_pb.reset(nullptr);
mount_thread_observer_.PostTask();
mount_thread_.task_runner()->PostTask(
FROM_HERE, base::Bind(&Service::DoUnmountEx, base::Unretained(this),
base::Passed(std::move(request_pb)),
base::Unretained(context)));
return TRUE;
}
gboolean Service::UpdateCurrentUserActivityTimestamp(gint time_shift_sec,
GError** error) {
base::AutoLock _lock(sessions_lock_);
for (const auto& session_pair : sessions_) {
session_pair.second->UpdateActivityTimestamp(time_shift_sec);
}
return TRUE;
}
gboolean Service::TpmIsReady(gboolean* OUT_ready, GError** error) {
*OUT_ready = tpm_init_->IsTpmReady();
return TRUE;
}
gboolean Service::TpmIsEnabled(gboolean* OUT_enabled, GError** error) {
*OUT_enabled = tpm_init_->IsTpmEnabled();
return TRUE;
}
gboolean Service::TpmGetPassword(gchar** OUT_password, GError** error) {
SecureBlob password;
if (!tpm_init_->GetTpmPassword(&password)) {
*OUT_password = NULL;
return TRUE;
}
// Convert to UTF-8 for sending over DBus. In case the original string
// contained only ASCII characters, the result will be identical to the
// original password.
SecureBlob utf8_password(
base::SysWideToUTF8(std::wstring(password.begin(), password.end())));
// Make sure we copy and NULL-terminate the entire UTF-8 string, even if
// there are 00 bytes in the middle of it. strndup/g_strndup would have
// stopped at the first 00. Can still be stripped later by DBus code, though.
size_t ret_size = utf8_password.size();
gchar* ret_str = g_new(gchar, ret_size + 1);
if (ret_str) {
memcpy(ret_str, utf8_password.char_data(), ret_size);
ret_str[ret_size] = 0;
}
*OUT_password = ret_str;
return TRUE;
}
gboolean Service::TpmIsOwned(gboolean* OUT_owned, GError** error) {
*OUT_owned = tpm_init_->IsTpmOwned();
return TRUE;
}
gboolean Service::TpmIsBeingOwned(gboolean* OUT_owning, GError** error) {
ReportDeprecatedApiCalled(DeprecatedApiEvent::kTpmIsBeingOwned);
*OUT_owning = tpm_init_->IsTpmBeingOwned();
return TRUE;
}
gboolean Service::TpmCanAttemptOwnership(GError** error) {
if (!tpm_init_->OwnershipRequested()) {
ReportTimerStart(kTpmTakeOwnershipTimer);
tpm_init_->AsyncTakeOwnership();
}
return TRUE;
}
gboolean Service::TpmClearStoredPassword(GError** error) {
tpm_init_->ClearStoredTpmPassword();
return TRUE;
}
gboolean Service::TpmGetVersionStructured(guint32* OUT_family,
guint64* OUT_spec_level,
guint32* OUT_manufacturer,
guint32* OUT_tpm_model,
guint64* OUT_firmware_version,
gchar** OUT_vendor_specific,
GError** error) {
cryptohome::Tpm::TpmVersionInfo version_info;
if (!tpm_init_->GetVersion(&version_info)) {
LOG(ERROR) << "Could not get TPM version information.";
*OUT_family = 0;
*OUT_spec_level = 0;
*OUT_manufacturer = 0;
*OUT_tpm_model = 0;
*OUT_firmware_version = 0;
*OUT_vendor_specific = nullptr;
return FALSE;
}
*OUT_family = version_info.family;
*OUT_spec_level = version_info.spec_level;
*OUT_manufacturer = version_info.manufacturer;
*OUT_tpm_model = version_info.tpm_model;
*OUT_firmware_version = version_info.firmware_version;
std::string vendor_specific_hex = base::HexEncode(
version_info.vendor_specific.data(), version_info.vendor_specific.size());
*OUT_vendor_specific = g_strdup(vendor_specific_hex.c_str());
return TRUE;
}
// Returns true if all Pkcs11 tokens are ready.
gboolean Service::Pkcs11IsTpmTokenReady(gboolean* OUT_ready, GError** error) {
*OUT_ready = TRUE;
base::AutoLock _lock(sessions_lock_);
for (const auto& session_pair : sessions_) {
UserSession* session = session_pair.second.get();
bool ok = (session->GetMount()->pkcs11_state() ==
cryptohome::Mount::kIsInitialized);
*OUT_ready = *OUT_ready && ok;
}
return TRUE;
}
gboolean Service::Pkcs11GetTpmTokenInfo(gchar** OUT_label,
gchar** OUT_user_pin,
gint* OUT_slot,
GError** error) {
std::string label, pin;
pkcs11_init_->GetTpmTokenInfo(&label, &pin);
*OUT_label = g_strdup(reinterpret_cast<const gchar*>(label.c_str()));
*OUT_user_pin = g_strdup(reinterpret_cast<const gchar*>(pin.c_str()));
*OUT_slot = -1;
CK_SLOT_ID slot;
if (pkcs11_init_->GetTpmTokenSlotForPath(FilePath(chaps::kSystemTokenPath),
&slot))
*OUT_slot = slot;
return TRUE;
}
gboolean Service::Pkcs11GetTpmTokenInfoForUser(gchar* IN_username,
gchar** OUT_label,
gchar** OUT_user_pin,
gint* OUT_slot,
GError** error) {
const std::string username = reinterpret_cast<const char*>(IN_username);
std::string label, pin;
pkcs11_init_->GetTpmTokenInfoForUser(username, &label, &pin);
*OUT_label = g_strdup(reinterpret_cast<const gchar*>(label.c_str()));
*OUT_user_pin = g_strdup(reinterpret_cast<const gchar*>(pin.c_str()));
*OUT_slot = -1;
CK_SLOT_ID slot;
FilePath token_path = homedirs_->GetChapsTokenDir(username);
if (pkcs11_init_->GetTpmTokenSlotForPath(token_path, &slot))
*OUT_slot = slot;
return TRUE;
}
gboolean Service::Pkcs11Terminate(gchar* username, GError** error) {
base::AutoLock _lock(sessions_lock_);
for (const auto& session_pair : sessions_)
session_pair.second->GetMount()->RemovePkcs11Token();
return TRUE;
}
gboolean Service::InstallAttributesGet(gchar* name,
GArray** OUT_value,
gboolean* OUT_successful,
GError** error) {
brillo::Blob value;
*OUT_successful = install_attrs_->Get(name, &value);
// We must set the GArray now because if we return without setting it,
// dbus-glib loops forever.
*OUT_value = g_array_new(false, false, sizeof(value.front()));
if (!(*OUT_value)) {
return FALSE;
}
if (*OUT_successful) {
g_array_append_vals(*OUT_value, value.data(), value.size());
}
return TRUE;
}
gboolean Service::InstallAttributesSet(gchar* name,
GArray* value,
gboolean* OUT_successful,
GError** error) {
// Convert from GArray to vector
brillo::Blob value_blob;
value_blob.assign(value->data, value->data + value->len);
*OUT_successful = install_attrs_->Set(name, value_blob);
return TRUE;
}
gboolean Service::InstallAttributesFinalize(gboolean* OUT_finalized,
GError** error) {
*OUT_finalized = install_attrs_->Finalize();
// Check if the machine is enterprise owned and report this to mount_.
DetectEnterpriseOwnership();
return TRUE;
}
gboolean Service::InstallAttributesCount(gint* OUT_count, GError** error) {
// TODO(wad) for all of these functions return error on uninit.
// Follow the CHROMEOS_LOGIN_ERROR quark example in brillo/dbus/
*OUT_count = install_attrs_->Count();
return TRUE;
}
gboolean Service::InstallAttributesIsReady(gboolean* OUT_ready,
GError** error) {
*OUT_ready =
(install_attrs_->status() != InstallAttributes::Status::kUnknown &&
install_attrs_->status() != InstallAttributes::Status::kTpmNotOwned);
return TRUE;
}
gboolean Service::InstallAttributesIsSecure(gboolean* OUT_is_secure,
GError** error) {
*OUT_is_secure = (install_attrs_->is_secure() == true);
return TRUE;
}
gboolean Service::InstallAttributesIsInvalid(gboolean* OUT_is_invalid,
GError** error) {
// Is true after a failed init or prior to Init().
*OUT_is_invalid =
(install_attrs_->status() == InstallAttributes::Status::kInvalid);
return TRUE;
}
gboolean Service::InstallAttributesIsFirstInstall(
gboolean* OUT_is_first_install, GError** error) {
*OUT_is_first_install =
(install_attrs_->status() == InstallAttributes::Status::kFirstInstall);
return TRUE;
}
void Service::DoSignBootLockbox(const brillo::Blob& request,
DBusGMethodInvocation* context) {
ReportDeprecatedApiCalled(DeprecatedApiEvent::kSignBootLockbox);
SignBootLockboxRequest request_pb;
if (!request_pb.ParseFromArray(request.data(), request.size()) ||
!request_pb.has_data()) {
SendInvalidArgsReply(context, "Bad SignBootLockboxRequest");
return;
}
BaseReply reply;
SecureBlob signature;
if (!boot_lockbox_->Sign(brillo::BlobFromString(request_pb.data()),
&signature)) {
reply.set_error(CRYPTOHOME_ERROR_LOCKBOX_CANNOT_SIGN);
} else {
reply.MutableExtension(SignBootLockboxReply::reply)
->set_signature(signature.to_string());
}
SendReply(context, reply);
}
gboolean Service::SignBootLockbox(const GArray* request,
DBusGMethodInvocation* context) {
PostTask(FROM_HERE,
base::Bind(&Service::DoSignBootLockbox, base::Unretained(this),
brillo::Blob(request->data, request->data + request->len),
base::Unretained(context)));
return TRUE;
}
void Service::DoVerifyBootLockbox(const brillo::Blob& request,
DBusGMethodInvocation* context) {
ReportDeprecatedApiCalled(DeprecatedApiEvent::kVerifyBootLockbox);
VerifyBootLockboxRequest request_pb;
if (!request_pb.ParseFromArray(request.data(), request.size()) ||
!request_pb.has_data() || !request_pb.has_signature()) {
SendInvalidArgsReply(context, "Bad VerifyBootLockboxRequest");
return;
}
BaseReply reply;
if (!boot_lockbox_->Verify(brillo::BlobFromString(request_pb.data()),
SecureBlob(request_pb.signature()))) {
reply.set_error(CRYPTOHOME_ERROR_LOCKBOX_SIGNATURE_INVALID);
}
SendReply(context, reply);
}
gboolean Service::VerifyBootLockbox(const GArray* request,
DBusGMethodInvocation* context) {
PostTask(FROM_HERE,
base::Bind(&Service::DoVerifyBootLockbox, base::Unretained(this),
brillo::Blob(request->data, request->data + request->len),
base::Unretained(context)));
return TRUE;
}
void Service::DoFinalizeBootLockbox(const brillo::Blob& request,
DBusGMethodInvocation* context) {
ReportDeprecatedApiCalled(DeprecatedApiEvent::kFinalizeBootLockbox);
FinalizeBootLockboxRequest request_pb;
if (!request_pb.ParseFromArray(request.data(), request.size())) {
SendInvalidArgsReply(context, "Bad FinalizeBootLockboxRequest");
return;
}
BaseReply reply;
if (!boot_lockbox_->FinalizeBoot()) {
reply.set_error(CRYPTOHOME_ERROR_TPM_COMM_ERROR);
}
SendReply(context, reply);
}
gboolean Service::FinalizeBootLockbox(const GArray* request,
DBusGMethodInvocation* context) {
PostTask(FROM_HERE,
base::Bind(&Service::DoFinalizeBootLockbox, base::Unretained(this),
brillo::Blob(request->data, request->data + request->len),
base::Unretained(context)));
return TRUE;
}
void Service::DoGetBootAttribute(const brillo::Blob& request,
DBusGMethodInvocation* context) {
ReportDeprecatedApiCalled(DeprecatedApiEvent::kGetBootAttribute);
GetBootAttributeRequest request_pb;
if (!request_pb.ParseFromArray(request.data(), request.size())) {
SendInvalidArgsReply(context, "Bad GetBootAttributeRequest");
return;
}
BaseReply reply;
std::string value;
if (!boot_attributes_->Get(request_pb.name(), &value)) {
reply.set_error(CRYPTOHOME_ERROR_BOOT_ATTRIBUTE_NOT_FOUND);
} else {
reply.MutableExtension(GetBootAttributeReply::reply)->set_value(value);
}
SendReply(context, reply);
}
gboolean Service::GetBootAttribute(const GArray* request,
DBusGMethodInvocation* context) {
PostTask(FROM_HERE,
base::Bind(&Service::DoGetBootAttribute, base::Unretained(this),
brillo::Blob(request->data, request->data + request->len),
base::Unretained(context)));
return TRUE;
}
void Service::DoSetBootAttribute(const brillo::Blob& request,
DBusGMethodInvocation* context) {
ReportDeprecatedApiCalled(DeprecatedApiEvent::kSetBootAttribute);
SetBootAttributeRequest request_pb;
if (!request_pb.ParseFromArray(request.data(), request.size())) {
SendInvalidArgsReply(context, "Bad SetBootAttributeRequest");
return;
}
BaseReply reply;
boot_attributes_->Set(request_pb.name(), request_pb.value());
SendReply(context, reply);
}
gboolean Service::SetBootAttribute(const GArray* request,
DBusGMethodInvocation* context) {
PostTask(FROM_HERE,
base::Bind(&Service::DoSetBootAttribute, base::Unretained(this),
brillo::Blob(request->data, request->data + request->len),
base::Unretained(context)));
return TRUE;
}
void Service::DoFlushAndSignBootAttributes(const brillo::Blob& request,
DBusGMethodInvocation* context) {
ReportDeprecatedApiCalled(DeprecatedApiEvent::kFlushAndSignBootAttributes);
FlushAndSignBootAttributesRequest request_pb;
if (!request_pb.ParseFromArray(request.data(), request.size())) {
SendInvalidArgsReply(context, "Bad FlushAndSignBootAttributesRequest");
return;
}
BaseReply reply;
if (!boot_attributes_->FlushAndSign()) {
reply.set_error(CRYPTOHOME_ERROR_BOOT_ATTRIBUTES_CANNOT_SIGN);
}
SendReply(context, reply);
}
gboolean Service::FlushAndSignBootAttributes(const GArray* request,
DBusGMethodInvocation* context) {
PostTask(
FROM_HERE,
base::Bind(&Service::DoFlushAndSignBootAttributes, base::Unretained(this),
brillo::Blob(request->data, request->data + request->len),
base::Unretained(context)));
return TRUE;
}
void Service::DoGetLoginStatus(const brillo::SecureBlob& request,
DBusGMethodInvocation* context) {
GetLoginStatusRequest request_pb;
if (!request_pb.ParseFromArray(request.data(), request.size())) {
SendInvalidArgsReply(context, "Bad GetLoginStatusRequest");
return;
}
BaseReply reply;
std::string owner;
reply.MutableExtension(GetLoginStatusReply::reply)
->set_owner_user_exists(homedirs_->GetPlainOwner(&owner));
reply.MutableExtension(GetLoginStatusReply::reply)
->set_boot_lockbox_finalized(boot_lockbox_->IsFinalized());
reply.MutableExtension(GetLoginStatusReply::reply)
->set_is_locked_to_single_user(
platform_->FileExists(base::FilePath(kLockedToSingleUserFile)));
SendReply(context, reply);
}
gboolean Service::GetLoginStatus(const GArray* request,
DBusGMethodInvocation* context) {
PostTask(FROM_HERE,
base::Bind(&Service::DoGetLoginStatus, base::Unretained(this),
SecureBlob(request->data, request->data + request->len),
base::Unretained(context)));
return TRUE;
}
void Service::DoTpmAttestationGetEnrollmentPreparationsEx(
const brillo::Blob& request, DBusGMethodInvocation* context) {
AttestationGetEnrollmentPreparationsRequest request_pb;
if (!request_pb.ParseFromArray(request.data(), request.size())) {
SendInvalidArgsReply(context,
"Bad AttestationGetEnrollmentPreparationsRequest");
return;
}
BaseReply reply;
AttestationGetEnrollmentPreparationsReply* extension =
reply.MutableExtension(AttestationGetEnrollmentPreparationsReply::reply);
if (!AttestationGetEnrollmentPreparations(request_pb, extension)) {
reply.set_error(CRYPTOHOME_ERROR_INTERNAL_ATTESTATION_ERROR);
}
SendReply(context, reply);
}
gboolean Service::TpmAttestationGetEnrollmentPreparationsEx(
const GArray* request, DBusGMethodInvocation* context) {
mount_thread_observer_.PostTask();
mount_thread_.task_runner()->PostTask(
FROM_HERE,
base::Bind(&Service::DoTpmAttestationGetEnrollmentPreparationsEx,
base::Unretained(this),
brillo::Blob(request->data, request->data + request->len),
base::Unretained(context)));
return TRUE;
}
void Service::DoGetTpmStatus(const brillo::SecureBlob& request,
DBusGMethodInvocation* context) {
GetTpmStatusRequest request_pb;
if (!request_pb.ParseFromArray(request.data(), request.size())) {
SendInvalidArgsReply(context, "Bad GetTpmStatusRequest");
return;
}
BaseReply reply;
GetTpmStatusReply* extension =
reply.MutableExtension(GetTpmStatusReply::reply);
extension->set_enabled(tpm_init_->IsTpmEnabled());
extension->set_owned(tpm_init_->IsTpmOwned());
SecureBlob owner_password;
if (tpm_init_->GetTpmPassword(&owner_password)) {
extension->set_initialized(false);
extension->set_owner_password(owner_password.to_string());
} else {
// Initialized is true only when the TPM is owned and the owner password has
// already been destroyed.
extension->set_initialized(extension->owned());
}
extension->set_has_reset_lock_permissions(tpm_->HasResetLockPermissions());
int counter;
int threshold;
bool lockout;
int seconds_remaining;
if (tpm_->GetDictionaryAttackInfo(&counter, &threshold, &lockout,
&seconds_remaining)) {
extension->set_dictionary_attack_counter(counter);
extension->set_dictionary_attack_threshold(threshold);
extension->set_dictionary_attack_lockout_in_effect(lockout);
extension->set_dictionary_attack_lockout_seconds_remaining(
seconds_remaining);
}
extension->set_install_lockbox_finalized(
extension->owned() &&
install_attrs_->status() == InstallAttributes::Status::kValid);
extension->set_boot_lockbox_finalized(boot_lockbox_->IsFinalized());
extension->set_is_locked_to_single_user(
base::PathExists(base::FilePath(kLockedToSingleUserFile)));
AttestationGetTpmStatus(extension);
SendReply(context, reply);
}
gboolean Service::GetTpmStatus(const GArray* request,
DBusGMethodInvocation* context) {
PostTask(FROM_HERE,
base::Bind(&Service::DoGetTpmStatus, base::Unretained(this),
SecureBlob(request->data, request->data + request->len),
base::Unretained(context)));
return TRUE;
}
void Service::OnStartFingerprintAuthSessionDone(DBusGMethodInvocation* context,
bool success) {
VLOG(1) << "Start fingerprint auth session result: " << success;
BaseReply reply;
if (!success)
reply.set_error(CRYPTOHOME_ERROR_FINGERPRINT_ERROR_INTERNAL);
SendReply(context, reply);
}
void Service::DoStartFingerprintAuthSession(
std::unique_ptr<AccountIdentifier> identifier,
std::unique_ptr<StartFingerprintAuthSessionRequest> request,
DBusGMethodInvocation* context) {
if (!identifier || !request) {
SendInvalidArgsReply(context, "Failed to parse parameters.");
return;
}
if (GetAccountId(*identifier).empty()) {
SendInvalidArgsReply(context, "No email supplied");
return;
}
BaseReply reply;
const std::string obfuscated_username =
SanitizeUserNameWithSalt(GetAccountId(*identifier), system_salt_);
if (!homedirs_->Exists(obfuscated_username)) {
reply.set_error(CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND);
SendReply(context, reply);
return;
}
fingerprint_manager_->StartAuthSessionAsyncForUser(
obfuscated_username,
base::Bind(&Service::OnStartFingerprintAuthSessionDone,
base::Unretained(this), context));
}
gboolean Service::StartFingerprintAuthSession(
const GArray* account_id,
const GArray* start_auth_session_request,
DBusGMethodInvocation* context) {
std::unique_ptr<AccountIdentifier> identifier(new AccountIdentifier);
std::unique_ptr<StartFingerprintAuthSessionRequest> request(
new StartFingerprintAuthSessionRequest);
// On parsing failure, pass along a NULL.
if (!identifier->ParseFromArray(account_id->data, account_id->len)) {
identifier.reset(NULL);
}
if (!request->ParseFromArray(start_auth_session_request->data,
start_auth_session_request->len)) {
request.reset(NULL);
}
// If PBs don't parse, the validation in the handler will catch it.
PostTask(
FROM_HERE,
base::Bind(&Service::DoStartFingerprintAuthSession,
base::Unretained(this), base::Passed(std::move(identifier)),
base::Passed(std::move(request)), base::Unretained(context)));
return TRUE;
}
void Service::DoEndFingerprintAuthSession(
std::unique_ptr<EndFingerprintAuthSessionRequest> request,
DBusGMethodInvocation* context) {
if (!request) {
SendInvalidArgsReply(context, "Failed to parse parameters.");
return;
}
fingerprint_manager_->EndAuthSession();
SendReply(context, BaseReply());
}
gboolean Service::EndFingerprintAuthSession(
const GArray* end_auth_session_request, DBusGMethodInvocation* context) {
std::unique_ptr<EndFingerprintAuthSessionRequest> request(
new EndFingerprintAuthSessionRequest);
// On parsing failure, pass along a NULL.
if (!request->ParseFromArray(end_auth_session_request->data,
end_auth_session_request->len)) {
request.reset(NULL);
}
// If PBs don't parse, the validation in the handler will catch it.
PostTask(
FROM_HERE,
base::Bind(&Service::DoEndFingerprintAuthSession, base::Unretained(this),
base::Passed(std::move(request)), base::Unretained(context)));
return TRUE;
}
gboolean Service::GetWebAuthnSecret(const GArray* account_id,
const GArray* get_secret_request,
DBusGMethodInvocation* context) {
std::unique_ptr<AccountIdentifier> identifier(new AccountIdentifier);
std::unique_ptr<GetWebAuthnSecretRequest> request(
new GetWebAuthnSecretRequest);
// On parsing failure, pass along a NULL.
if (!identifier->ParseFromArray(account_id->data, account_id->len)) {
SendInvalidArgsReply(context, "Cannot parse account_id");
return TRUE;
}
if (!request->ParseFromArray(get_secret_request->data,
get_secret_request->len)) {
SendInvalidArgsReply(context, "Cannot parse GetWebAuthnSecret request");
return TRUE;
}
scoped_refptr<UserSession> session =
GetUserSession(GetAccountId(*identifier));
BaseReply reply;
if (!session || !session->GetMount()) {
reply.set_error(CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND);
SendReply(context, reply);
return TRUE;
}
std::unique_ptr<brillo::SecureBlob> secret =
session->GetMount()->GetWebAuthnSecret();
if (!secret) {
reply.set_error(CRYPTOHOME_ERROR_KEY_NOT_FOUND);
SendReply(context, reply);
return TRUE;
}
GetWebAuthnSecretReply* extension =
reply.MutableExtension(GetWebAuthnSecretReply::reply);
extension->set_webauthn_secret(secret->to_string());
SendReply(context, reply);
return TRUE;
}
void Service::DoGetFirmwareManagementParameters(
const brillo::SecureBlob& request, DBusGMethodInvocation* context) {
GetFirmwareManagementParametersRequest request_pb;
if (!request_pb.ParseFromArray(request.data(), request.size())) {
SendInvalidArgsReply(context, "Bad GetFirmwareManagementParametersRequest");
return;
}
BaseReply reply;
GetFirmwareManagementParametersReply* extension =
reply.MutableExtension(GetFirmwareManagementParametersReply::reply);
if (!firmware_management_parameters_->Load()) {
reply.set_error(CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_INVALID);
SendReply(context, reply);
return;
}
uint32_t flags;
if (firmware_management_parameters_->GetFlags(&flags)) {
extension->set_flags(flags);
}
brillo::Blob hash;
if (firmware_management_parameters_->GetDeveloperKeyHash(&hash)) {
extension->set_developer_key_hash(brillo::BlobToString(hash));
}
SendReply(context, reply);
}
gboolean Service::GetFirmwareManagementParameters(
const GArray* request, DBusGMethodInvocation* context) {
PostTask(FROM_HERE,
base::Bind(&Service::DoGetFirmwareManagementParameters,
base::Unretained(this),
SecureBlob(request->data, request->data + request->len),
base::Unretained(context)));
return TRUE;
}
void Service::DoSetFirmwareManagementParameters(
const brillo::SecureBlob& request, DBusGMethodInvocation* context) {
SetFirmwareManagementParametersRequest request_pb;
if (!request_pb.ParseFromArray(request.data(), request.size())) {
SendInvalidArgsReply(context, "Bad SetFirmwareManagementParametersRequest");
return;
}
BaseReply reply;
if (!firmware_management_parameters_->Create()) {
reply.set_error(
CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_CANNOT_STORE);
SendReply(context, reply);
return;
}
uint32_t flags = 0;
if (request_pb.has_flags()) {
flags = request_pb.flags();
}
std::unique_ptr<brillo::Blob> hash;
if (request_pb.has_developer_key_hash()) {
hash.reset(new brillo::Blob(
brillo::BlobFromString(request_pb.developer_key_hash())));
}
if (!firmware_management_parameters_->Store(flags, hash.get())) {
reply.set_error(
CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_CANNOT_STORE);
SendReply(context, reply);
return;
}
SendReply(context, reply);
}
gboolean Service::SetFirmwareManagementParameters(
const GArray* request, DBusGMethodInvocation* context) {
PostTask(FROM_HERE,
base::Bind(&Service::DoSetFirmwareManagementParameters,
base::Unretained(this),
SecureBlob(request->data, request->data + request->len),
base::Unretained(context)));
return TRUE;
}
void Service::DoRemoveFirmwareManagementParameters(
const brillo::SecureBlob& request, DBusGMethodInvocation* context) {
RemoveFirmwareManagementParametersRequest request_pb;
if (!request_pb.ParseFromArray(request.data(), request.size())) {
SendInvalidArgsReply(context,
"Bad RemoveFirmwareManagementParametersRequest");
return;
}
BaseReply reply;
if (!firmware_management_parameters_->Destroy()) {
reply.set_error(
CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_CANNOT_REMOVE);
SendReply(context, reply);
return;
}
SendReply(context, reply);
}
gboolean Service::RemoveFirmwareManagementParameters(
const GArray* request, DBusGMethodInvocation* context) {
PostTask(FROM_HERE,
base::Bind(&Service::DoRemoveFirmwareManagementParameters,
base::Unretained(this),
SecureBlob(request->data, request->data + request->len),
base::Unretained(context)));
return TRUE;
}
gboolean Service::GetStatusString(gchar** OUT_status, GError** error) {
base::Value mounts(base::Value::Type::LIST);
{
base::AutoLock _lock(sessions_lock_);
for (const auto& session_pair : sessions_) {
mounts.Append(session_pair.second->GetStatus());
}
}
auto attrs = install_attrs_->GetStatus();
Tpm::TpmStatusInfo tpm_status_info;
tpm_->GetStatus(tpm_init_->GetCryptohomeKey(), &tpm_status_info);
base::Value tpm(base::Value::Type::DICTIONARY);
tpm.SetBoolKey("can_connect", tpm_status_info.can_connect);
tpm.SetBoolKey("can_load_srk", tpm_status_info.can_load_srk);
tpm.SetBoolKey("can_load_srk_pubkey",
tpm_status_info.can_load_srk_public_key);
tpm.SetBoolKey("srk_vulnerable_roca", tpm_status_info.srk_vulnerable_roca);
tpm.SetBoolKey("has_cryptohome_key", tpm_status_info.has_cryptohome_key);
tpm.SetBoolKey("can_encrypt", tpm_status_info.can_encrypt);
tpm.SetBoolKey("can_decrypt", tpm_status_info.can_decrypt);
tpm.SetBoolKey("has_context", tpm_status_info.this_instance_has_context);
tpm.SetBoolKey("has_key_handle",
tpm_status_info.this_instance_has_key_handle);
tpm.SetIntKey("last_error", tpm_status_info.last_tpm_error);
tpm.SetBoolKey("enabled", tpm_->IsEnabled());
tpm.SetBoolKey("owned", tpm_->IsOwned());
tpm.SetBoolKey("being_owned", tpm_->IsBeingOwned());
base::Value dv(base::Value::Type::DICTIONARY);
dv.SetKey("mounts", std::move(mounts));
dv.SetKey("installattrs", std::move(attrs));
dv.SetKey("tpm", std::move(tpm));
std::string json;
base::JSONWriter::WriteWithOptions(dv, base::JSONWriter::OPTIONS_PRETTY_PRINT,
&json);
*OUT_status = g_strdup(json.c_str());
return TRUE;
}
void Service::DoAutoCleanup() {
disk_cleanup_->FreeDiskSpace();
last_auto_cleanup_time_ = platform_->GetCurrentTime();
}
void Service::UpdateCurrentUserActivityTimestamp() {
base::AutoLock _lock(sessions_lock_);
for (const auto& session_pair : sessions_) {
session_pair.second->UpdateActivityTimestamp(0);
}
}
// Called on Mount thread.
void Service::LowDiskCallback() {
bool low_disk_space_signal_emitted = false;
auto free_disk_space = disk_cleanup_->AmountOfFreeDiskSpace();
auto free_space_state = disk_cleanup_->GetFreeDiskSpaceState(free_disk_space);
if (free_space_state == DiskCleanup::FreeSpaceState::kError) {
LOG(ERROR) << "Error getting free disk space";
} else if (free_space_state ==
DiskCleanup::FreeSpaceState::kNeedNormalCleanup ||
free_space_state ==
DiskCleanup::FreeSpaceState::kNeedAggressiveCleanup) {
g_signal_emit(cryptohome_, low_disk_space_signal_,
0 /* signal detail (not used) */,
static_cast<uint64_t>(free_disk_space.value()));
low_disk_space_signal_emitted = true;
}
const base::Time current_time = platform_->GetCurrentTime();
const bool time_for_auto_cleanup =
current_time - last_auto_cleanup_time_ >
base::TimeDelta::FromMilliseconds(kAutoCleanupPeriodMS);
// We shouldn't repeat cleanups on every minute if the disk space
// stays below the threshold. Trigger it only if there was no notification
// previously or if enterprise owned and free space can be reclaimed.
const bool early_cleanup_needed =
low_disk_space_signal_emitted &&
(!low_disk_space_signal_was_emitted_ ||
disk_cleanup_->IsFreeableDiskSpaceAvailable());
if (time_for_auto_cleanup || early_cleanup_needed)
DoAutoCleanup();
const bool time_for_user_activity_period_update =
current_time - last_user_activity_timestamp_time_ >
base::TimeDelta::FromHours(kUpdateUserActivityPeriodHours);
if (time_for_user_activity_period_update) {
last_user_activity_timestamp_time_ = current_time;
UpdateCurrentUserActivityTimestamp();
}
low_disk_space_signal_was_emitted_ = low_disk_space_signal_emitted;
// Schedule our next call. If the thread is terminating, we would
// not be called. We use base::Unretained here because the Service object is
// never destroyed.
// We don't care about the parallel delay tasks number. Don't increase the
// parallel tasks count here.
mount_thread_.task_runner()->PostDelayedTask(
FROM_HERE, base::Bind(&Service::LowDiskCallback, base::Unretained(this)),
base::TimeDelta::FromMilliseconds(low_disk_notification_period_ms_));
}
void Service::ResetDictionaryAttackMitigation() {
if (!tpm_init_ || !tpm_init_->IsTpmReady()) {
return;
}
// If tpm_manager exists, the DA reset and UMA reporting should happen there.
if (tpm_ && tpm_->DoesUseTpmManager()) {
// tpm_manager doesn't take delegate as input.
brillo::Blob unused_blob;
if (!tpm_->ResetDictionaryAttackMitigation(unused_blob, unused_blob)) {
LOG(WARNING) << "Failed to reset DA.";
}
return;
}
int counter = 0;
int threshold;
int seconds_remaining;
bool lockout;
if (!tpm_->GetDictionaryAttackInfo(&counter, &threshold, &lockout,
&seconds_remaining)) {
ReportDictionaryAttackResetStatus(kCounterQueryFailed);
return;
}
ReportDictionaryAttackCounter(counter);
if (counter == 0) {
ReportDictionaryAttackResetStatus(kResetNotNecessary);
return;
}
brillo::Blob delegate_blob, delegate_secret;
bool has_reset_lock_permissions = false;
if (!AttestationGetDelegateCredentials(&delegate_blob, &delegate_secret,
&has_reset_lock_permissions)) {
ReportDictionaryAttackResetStatus(kDelegateNotAvailable);
return;
}
if (!has_reset_lock_permissions) {
ReportDictionaryAttackResetStatus(kDelegateNotAllowed);
return;
}
if (!tpm_->ResetDictionaryAttackMitigation(delegate_blob, delegate_secret)) {
if (!tpm_->IsCurrentPCR0ValueValid()) {
ReportDictionaryAttackResetStatus(kInvalidPcr0State);
} else {
ReportDictionaryAttackResetStatus(kResetAttemptFailed);
}
return;
}
ReportDictionaryAttackResetStatus(kResetAttemptSucceeded);
}
void Service::DetectEnterpriseOwnership() {
static const char true_str[] = "true";
const brillo::Blob true_value(true_str, true_str + base::size(true_str));
brillo::Blob value;
if (install_attrs_->Get("enterprise.owned", &value) && value == true_value) {
enterprise_owned_ = true;
homedirs_->set_enterprise_owned(true);
}
}
scoped_refptr<cryptohome::Mount> Service::CreateMount(
const std::string& username) {
scoped_refptr<cryptohome::Mount> m;
// TODO(dlunev): Decide if finalization should be moved to MountFactory.
EnsureBootLockboxFinalized();
m = mount_factory_->New(platform_, homedirs_);
m->set_legacy_mount(legacy_mount_);
m->set_bind_mount_downloads(bind_mount_downloads_);
if (!m->Init()) {
return nullptr;
}
return m;
}
scoped_refptr<UserSession> Service::GetOrCreateUserSession(
const std::string& username) {
base::AutoLock _lock(sessions_lock_);
if (sessions_.count(username) == 0U) {
// We don't have a mount associated with |username|, let's create one.
scoped_refptr<cryptohome::Mount> m = CreateMount(username);
if (!m) {
return nullptr;
}
sessions_[username] = new UserSession(homedirs_, system_salt_, m);
}
return sessions_[username];
}
scoped_refptr<UserSession> Service::GetUserSessionForMount(
cryptohome::Mount* mount) {
base::AutoLock _lock(sessions_lock_);
// We assume that mount might be not initialized proper thus just iterate
// through session and compare.
for (const auto& session_pair : sessions_) {
const scoped_refptr<UserSession> session = session_pair.second;
if (session->GetMount().get() == mount) {
return session;
}
}
return nullptr;
}
bool Service::RemoveUserSession(const std::string& username) {
base::AutoLock _lock(sessions_lock_);
if (sessions_.count(username) != 0) {
return (1U == sessions_.erase(username));
}
return true;
}
bool Service::RemoveUserSession(UserSession* session) {
base::AutoLock _lock(sessions_lock_);
for (auto it = sessions_.begin(); it != sessions_.end(); ++it) {
if (it->second.get() == session) {
sessions_.erase(it);
return true;
}
}
return false;
}
void Service::EnsureBootLockboxFinalized() {
if (boot_lockbox_ && !boot_lockbox_->FinalizeBoot()) {
LOG(WARNING) << "Failed to finalize boot lockbox when mounting guest "
"cryptohome";
}
#if USE_TPM2
// Lock NVRamBootLockbox
auto nvram_boot_lockbox_client = BootLockboxClient::CreateBootLockboxClient();
if (!nvram_boot_lockbox_client) {
LOG(WARNING) << "Failed to create nvram_boot_lockbox_client";
return;
}
if (!nvram_boot_lockbox_client->Finalize()) {
LOG(WARNING) << "Failed to finalize nvram lockbox.";
}
#endif // USE_TMP2
}
bool Service::RemoveAllMounts(bool unmount) {
bool ok = true;
base::AutoLock _lock(sessions_lock_);
for (auto it = sessions_.begin(); it != sessions_.end();) {
scoped_refptr<UserSession> session = it->second;
if (unmount && session->GetMount()->IsMounted()) {
if (session->GetMount()->pkcs11_state() ==
cryptohome::Mount::kIsBeingInitialized) {
// Reset the state.
session->GetMount()->set_pkcs11_state(
cryptohome::Mount::kUninitialized);
// And also reset the global failure reported state.
// TODO(wad,ellyjones,dkrahn) De-globalize this when Chaps support
// multiple mounts.
reported_pkcs11_init_fail_ = false;
}
ok = ok && session->Unmount();
}
sessions_.erase(it++);
}
return ok;
}
bool Service::GetMountPointForUser(const std::string& username,
FilePath* path) {
scoped_refptr<UserSession> session;
session = GetUserSession(username);
if (!session.get() || !session->GetMount()->IsMounted())
return false;
*path = session->GetMount()->mount_point();
return true;
}
scoped_refptr<UserSession> Service::GetUserSession(
const std::string& username) {
scoped_refptr<UserSession> session = nullptr;
base::AutoLock _lock(sessions_lock_);
if (sessions_.count(username) == 1) {
session = sessions_[username];
}
return session;
}
bool Service::CreatePublicMountSaltIfNeeded() {
if (!public_mount_salt_.empty())
return true;
FilePath saltfile(kPublicMountSaltFilePath);
return crypto_->GetOrCreateSalt(saltfile, CRYPTOHOME_DEFAULT_SALT_LENGTH,
false, &public_mount_salt_);
}
bool Service::GetPublicMountPassKey(const std::string& public_mount_id,
std::string* public_mount_passkey) {
if (!CreatePublicMountSaltIfNeeded())
return false;
SecureBlob passkey;
Crypto::PasswordToPasskey(public_mount_id.c_str(), public_mount_salt_,
&passkey);
*public_mount_passkey = passkey.to_string();
return true;
}
void Service::DispatchEventsForTesting() {
event_source_.HandleDispatch();
}
gboolean Service::MigrateToDircrypto(const GArray* account_id,
const GArray* migrate_request,
GError** error) {
auto identifier = std::make_unique<AccountIdentifier>();
if (!identifier->ParseFromArray(account_id->data, account_id->len)) {
LOG(ERROR) << "Failed to parse identifier.";
return FALSE;
}
auto request = std::make_unique<MigrateToDircryptoRequest>();
if (!request->ParseFromArray(migrate_request->data, migrate_request->len)) {
LOG(ERROR) << "Failed to parse migrate_request.";
return FALSE;
}
MigrationType migration_type = request->minimal_migration()
? MigrationType::MINIMAL
: MigrationType::FULL;
// This Dbus method just kicks the migration task on the mount thread,
// and replies immediately.
PostTask(FROM_HERE,
base::Bind(&Service::DoMigrateToDircrypto, base::Unretained(this),
base::Owned(identifier.release()), migration_type));
return TRUE;
}
void Service::SendDircryptoMigrationProgressSignalProto(
const user_data_auth::DircryptoMigrationProgress& progress) {
SendDircryptoMigrationProgressSignal(
dircrypto_data_migrator::MigrationHelper::ConvertDircryptoMigrationStatus(
progress.status()),
progress.current_bytes(), progress.total_bytes());
}
void Service::DoMigrateToDircrypto(AccountIdentifier* identifier,
MigrationType migration_type) {
scoped_refptr<UserSession> session =
GetUserSession(GetAccountId(*identifier));
if (!session.get()) {
LOG(ERROR) << "Failed to get session.";
SendDircryptoMigrationProgressSignal(DIRCRYPTO_MIGRATION_FAILED, 0, 0);
return;
}
LOG(INFO) << "Migrating to dircrypto.";
if (!session->GetMount()->MigrateToDircrypto(
base::Bind(&Service::SendDircryptoMigrationProgressSignalProto,
base::Unretained(this)),
migration_type)) {
LOG(ERROR) << "Failed to migrate.";
SendDircryptoMigrationProgressSignal(DIRCRYPTO_MIGRATION_FAILED, 0, 0);
return;
}
LOG(INFO) << "Migration done.";
SendDircryptoMigrationProgressSignal(DIRCRYPTO_MIGRATION_SUCCESS, 0, 0);
}
gboolean Service::NeedsDircryptoMigration(const GArray* account_id,
gboolean* OUT_needs_migration,
GError** error) {
std::unique_ptr<AccountIdentifier> identifier(new AccountIdentifier);
if (!identifier->ParseFromArray(account_id->data, account_id->len)) {
LOG(ERROR) << "No user supplied.";
return FALSE;
}
const std::string obfuscated_username =
SanitizeUserNameWithSalt(GetAccountId(*identifier), system_salt_);
if (!homedirs_->Exists(obfuscated_username)) {
LOG(ERROR) << "Unknown user.";
return FALSE;
}
*OUT_needs_migration = !force_ecryptfs_ && homedirs_->NeedsDircryptoMigration(
obfuscated_username);
return TRUE;
}
gboolean Service::GetSupportedKeyPolicies(const GArray* request,
DBusGMethodInvocation* context) {
PostTask(
FROM_HERE,
base::Bind(&Service::DoGetSupportedKeyPolicies, base::Unretained(this),
std::string(request->data, request->data + request->len),
base::Unretained(context)));
return TRUE;
}
void Service::DoGetSupportedKeyPolicies(const std::string& request,
DBusGMethodInvocation* context) {
GetSupportedKeyPoliciesRequest request_pb;
if (!request_pb.ParseFromArray(request.data(), request.size())) {
SendInvalidArgsReply(context, "Bad GetSupportedKeyPoliciesRequest");
return;
}
BaseReply reply;
GetSupportedKeyPoliciesReply* extension =
reply.MutableExtension(GetSupportedKeyPoliciesReply::reply);
if (tpm_) {
if (tpm_->GetLECredentialBackend() &&
tpm_->GetLECredentialBackend()->IsSupported()) {
extension->set_low_entropy_credentials(true);
}
} else {
extension->set_low_entropy_credentials(false);
}
SendReply(context, reply);
}
bool Service::GetShouldMountAsEphemeral(const std::string& account_id,
bool is_ephemeral_mount_requested,
bool has_create_request,
bool* is_ephemeral,
MountError* error) const {
const bool is_or_will_be_owner = homedirs_->IsOrWillBeOwner(account_id);
if (is_ephemeral_mount_requested && is_or_will_be_owner) {
LOG(ERROR) << "An ephemeral cryptohome can only be mounted when the user "
"is not the owner.";
*error = MOUNT_ERROR_FATAL;
return false;
}
*is_ephemeral =
!is_or_will_be_owner &&
(homedirs_->AreEphemeralUsersEnabled() || is_ephemeral_mount_requested);
if (*is_ephemeral && !has_create_request) {
LOG(ERROR) << "An ephemeral cryptohome can only be mounted when its "
"creation on-the-fly is allowed.";
*error = MOUNT_ERROR_USER_DOES_NOT_EXIST;
return false;
}
return true;
}
int Service::NextSequence() {
// AtomicSequenceNumber is zero-based, so increment so that the sequence ids
// are one-based.
return sequence_holder_.GetNext() + 1;
}
gboolean Service::IsQuotaSupported(gboolean* OUT_quota_supported,
GError** error) {
*OUT_quota_supported = arc_disk_quota_->IsQuotaSupported();
return TRUE;
}
gboolean Service::GetCurrentSpaceForUid(guint32 uid,
gint64* OUT_cur_space,
GError** error) {
*OUT_cur_space = arc_disk_quota_->GetCurrentSpaceForUid(uid);
return TRUE;
}
gboolean Service::GetCurrentSpaceForGid(guint32 gid,
gint64* OUT_cur_space,
GError** error) {
*OUT_cur_space = arc_disk_quota_->GetCurrentSpaceForGid(gid);
return TRUE;
}
gboolean Service::GetCurrentSpaceForProjectId(guint32 project_id,
gint64* OUT_cur_space,
GError** error) {
*OUT_cur_space = arc_disk_quota_->GetCurrentSpaceForProjectId(project_id);
return TRUE;
}
gboolean Service::SetProjectId(guint project_id,
gint parent_path,
gchar* child_path,
GArray* account_id,
gboolean* OUT_success,
GError** error) {
std::unique_ptr<AccountIdentifier> identifier(new AccountIdentifier);
if (!identifier->ParseFromArray(account_id->data, account_id->len)) {
LOG(ERROR) << "Failed to parse identifier.";
return FALSE;
}
const std::string obfuscated_username =
SanitizeUserNameWithSalt(GetAccountId(*identifier), system_salt_);
*OUT_success = arc_disk_quota_->SetProjectId(
project_id, static_cast<SetProjectIdAllowedPathType>(parent_path),
FilePath(reinterpret_cast<const char*>(child_path)), obfuscated_username);
return TRUE;
}
gboolean Service::LockToSingleUserMountUntilReboot(
const GArray* request, DBusGMethodInvocation* context) {
LockToSingleUserMountUntilRebootRequest request_pb;
if (!request_pb.ParseFromArray(request->data, request->len)) {
SendInvalidArgsReply(context, "Bad DisableLoginUntilRebootRequest.");
return FALSE;
}
if (!request_pb.has_account_id()) {
SendInvalidArgsReply(context, "Missing account_id.");
return FALSE;
}
const std::string obfuscated_username = SanitizeUserNameWithSalt(
GetAccountId(request_pb.account_id()), system_salt_);
mount_thread_observer_.PostTask();
mount_thread_.task_runner()->PostTask(
FROM_HERE, base::Bind(&Service::DoLockToSingleUserMountUntilReboot,
base::Unretained(this), obfuscated_username,
base::Unretained(context)));
return TRUE;
}
void Service::DoGetRsuDeviceId(DBusGMethodInvocation* context) {
std::string device_id;
bool success = tpm_->GetRsuDeviceId(&device_id);
// This happens when device requests RSU lookup key too frequently.
if (!success) {
SendFailureReply(context, "Unable to retrieve lookup key!");
return;
}
BaseReply reply;
reply.MutableExtension(GetRsuDeviceIdReply::reply)
->set_rsu_device_id(device_id);
SendReply(context, reply);
}
gboolean Service::GetRsuDeviceId(const GArray* request,
DBusGMethodInvocation* context) {
PostTask(FROM_HERE,
base::Bind(&Service::DoGetRsuDeviceId, base::Unretained(this),
base::Unretained(context)));
return TRUE;
}
void Service::DoLockToSingleUserMountUntilReboot(
const std::string& obfuscated_username, DBusGMethodInvocation* context) {
homedirs_->SetLockedToSingleUser();
brillo::Blob pcr_value;
BaseReply reply;
LockToSingleUserMountUntilRebootReply* reply_extension =
reply.MutableExtension(LockToSingleUserMountUntilRebootReply::reply);
if (!tpm_->ReadPCR(kTpmSingleUserPCR, &pcr_value)) {
LOG(ERROR) << "Failed to read PCR";
reply_extension->set_result(FAILED_TO_READ_PCR);
reply.set_error(CRYPTOHOME_ERROR_TPM_COMM_ERROR);
} else if (pcr_value != brillo::Blob(pcr_value.size(), 0)) {
reply_extension->set_result(PCR_ALREADY_EXTENDED);
} else {
brillo::Blob extention_blob(obfuscated_username.begin(),
obfuscated_username.end());
if (tpm_->GetVersion() == cryptohome::Tpm::TPM_1_2) {
extention_blob = CryptoLib::Sha1(extention_blob);
}
if (!tpm_->ExtendPCR(kTpmSingleUserPCR, extention_blob)) {
reply_extension->set_result(FAILED_TO_EXTEND_PCR);
reply.set_error(CRYPTOHOME_ERROR_TPM_COMM_ERROR);
}
}
SendReply(context, reply);
}
gboolean Service::CheckHealth(const GArray* request,
DBusGMethodInvocation* context) {
CheckHealthRequest request_pb;
if (!request_pb.ParseFromArray(request->data, request->len)) {
SendInvalidArgsReply(context, "Bad CheckHealthRequest.");
return FALSE;
}
BaseReply reply;
CheckHealthReply* reply_extension =
reply.MutableExtension(CheckHealthReply::reply);
const bool is_powerwash_required = !crypto_->CanUnsealWithUserAuth();
reply_extension->set_requires_powerwash(is_powerwash_required);
SendReply(context, reply);
return TRUE;
}
void Service::set_cleanup_threshold(uint64_t cleanup_threshold) {
disk_cleanup_->set_cleanup_threshold(cleanup_threshold);
}
void Service::set_aggressive_cleanup_threshold(
uint64_t aggressive_cleanup_threshold) {
disk_cleanup_->set_aggressive_cleanup_threshold(aggressive_cleanup_threshold);
}
void Service::set_target_free_space(uint64_t target_free_space) {
disk_cleanup_->set_target_free_space(target_free_space);
}
void Service::PostTaskToEventLoop(base::OnceClosure task) {
event_source_.AddEvent(std::make_unique<ClosureEvent>(std::move(task)));
}
void Service::LogAsyncIdInfo(int async_id,
std::string name,
base::Time start_time) {
async_id_tracked_info_[async_id] = {name, start_time};
}
void Service::SendAsyncIdInfoToUma(int async_id, base::Time finished_time) {
auto it = async_id_tracked_info_.find(async_id);
if (it == async_id_tracked_info_.end()) {
LOG(WARNING) << __func__ << ": async_id: " << async_id << " not found.";
return;
}
const RequestTrackedInfo& info = it->second;
ReportAsyncDbusRequestTotalTime(info.name, finished_time - info.start_time);
async_id_tracked_info_.erase(it);
}
gboolean Service::ShutdownService(gpointer user_data) {
Service* service = reinterpret_cast<Service*>(user_data);
LOG(INFO) << "Service shutting down...";
service->Shutdown();
// Returns false because we only need to handle it once.
return false;
}
gboolean Service::StartAuthSession(const GArray* account_id,
const GArray* start_auth_session_request,
DBusGMethodInvocation* context) {
SendInvalidArgsReply(context, "Auth sessions is only used in UserDataAuth");
return FALSE;
}
} // namespace cryptohome