blob: 0c365aa10e5d85c4ee745bd50d5b6f812c1116f7 [file] [log] [blame]
// Copyright 2014 The Chromium 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 "chromeos/login/auth/cryptohome_authenticator.h"
#include <stdint.h>
#include <vector>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "chromeos/chromeos_switches.h"
#include "chromeos/cryptohome/async_method_caller.h"
#include "chromeos/cryptohome/cryptohome_parameters.h"
#include "chromeos/cryptohome/cryptohome_util.h"
#include "chromeos/cryptohome/homedir_methods.h"
#include "chromeos/cryptohome/system_salt_getter.h"
#include "chromeos/dbus/cryptohome_client.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/login/auth/auth_status_consumer.h"
#include "chromeos/login/auth/key.h"
#include "chromeos/login/auth/user_context.h"
#include "chromeos/login/login_state.h"
#include "chromeos/login_event_recorder.h"
#include "components/account_id/account_id.h"
#include "components/device_event_log/device_event_log.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user_names.h"
#include "components/user_manager/user_type.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
namespace chromeos {
namespace {
// The label used for the key derived from the user's GAIA credentials.
const char kCryptohomeGAIAKeyLabel[] = "gaia";
// The label used for the key generated by Cryptohome for public mount.
const char kCryptohomePublicMountKeyLabel[] = "publicmount";
// The name under which the type of key generated from the user's GAIA
// credentials is stored.
const char kKeyProviderDataTypeName[] = "type";
// The name under which the salt used to generate a key from the user's GAIA
// credentials is stored.
const char kKeyProviderDataSaltName[] = "salt";
// Name of UMA histogram.
const char kCryptohomeMigrationToGaiaId[] = "Cryptohome.MigrationToGaiaId";
// This enum is used to define the buckets for an enumerated UMA histogram.
// Hence,
// (a) existing enumerated constants should never be deleted or reordered, and
// (b) new constants should only be appended at the end of the enumeration.
//
// This must be kept in sync with enum CryptohomeMigrationToGaiaId in
// histograms.xml .
enum CryptohomeMigrationToGaiaId {
NOT_STARTED = 0,
ALREADY_MIGRATED = 1,
SUCCESS = 2,
FAILURE = 3,
ENTRIES_COUNT
};
// Report to UMA.
void UMACryptohomeMigrationToGaiaId(const CryptohomeMigrationToGaiaId status) {
UMA_HISTOGRAM_ENUMERATION(kCryptohomeMigrationToGaiaId, status,
CryptohomeMigrationToGaiaId::ENTRIES_COUNT);
}
// Returns a human-readable string describing |state|.
const char* AuthStateToString(CryptohomeAuthenticator::AuthState state) {
switch (state) {
case CryptohomeAuthenticator::CONTINUE:
return "CONTINUE";
case CryptohomeAuthenticator::NO_MOUNT:
return "NO_MOUNT";
case CryptohomeAuthenticator::FAILED_MOUNT:
return "FAILED_MOUNT";
case CryptohomeAuthenticator::FAILED_REMOVE:
return "FAILED_REMOVE";
case CryptohomeAuthenticator::FAILED_TMPFS:
return "FAILED_TMPFS";
case CryptohomeAuthenticator::FAILED_TPM:
return "FAILED_TPM";
case CryptohomeAuthenticator::CREATE_NEW:
return "CREATE_NEW";
case CryptohomeAuthenticator::RECOVER_MOUNT:
return "RECOVER_MONUT";
case CryptohomeAuthenticator::POSSIBLE_PW_CHANGE:
return "POSSIBLE_PW_CHANGE";
case CryptohomeAuthenticator::NEED_NEW_PW:
return "NEED_NEW_PW";
case CryptohomeAuthenticator::NEED_OLD_PW:
return "NEED_OLD_PW";
case CryptohomeAuthenticator::HAVE_NEW_PW:
return "HAVE_NEW_PW";
case CryptohomeAuthenticator::OFFLINE_LOGIN:
return "OFFLINE_LOGIN";
case CryptohomeAuthenticator::ONLINE_LOGIN:
return "ONLINE_LOGIN";
case CryptohomeAuthenticator::UNLOCK:
return "UNLOCK";
case CryptohomeAuthenticator::ONLINE_FAILED:
return "ONLINE_FAILED";
case CryptohomeAuthenticator::GUEST_LOGIN:
return "GUEST_LOGIN";
case CryptohomeAuthenticator::PUBLIC_ACCOUNT_LOGIN:
return "PUBLIC_ACCOUNT_LOGIN";
case CryptohomeAuthenticator::SUPERVISED_USER_LOGIN:
return "SUPERVISED_USER_LOGIN";
case CryptohomeAuthenticator::LOGIN_FAILED:
return "LOGIN_FAILED";
case CryptohomeAuthenticator::OWNER_REQUIRED:
return "OWNER_REQUIRED";
case CryptohomeAuthenticator::FAILED_USERNAME_HASH:
return "FAILED_USERNAME_HASH";
case CryptohomeAuthenticator::KIOSK_ACCOUNT_LOGIN:
return "KIOSK_ACCOUNT_LOGIN";
case CryptohomeAuthenticator::REMOVED_DATA_AFTER_FAILURE:
return "REMOVED_DATA_AFTER_FAILURE";
case CryptohomeAuthenticator::FAILED_OLD_ENCRYPTION:
return "FAILED_OLD_ENCRYPTION";
case CryptohomeAuthenticator::FAILED_PREVIOUS_MIGRATION_INCOMPLETE:
return "FAILED_PREVIOUS_MIGRATION_INCOMPLETE";
case CryptohomeAuthenticator::OFFLINE_NO_MOUNT:
return "OFFLINE_NO_MOUNT";
}
return "UNKNOWN";
}
// Hashes |key| with |system_salt| if it its type is KEY_TYPE_PASSWORD_PLAIN.
// Returns the keys unmodified otherwise.
std::unique_ptr<Key> TransformKeyIfNeeded(const Key& key,
const std::string& system_salt) {
std::unique_ptr<Key> result(new Key(key));
if (result->GetKeyType() == Key::KEY_TYPE_PASSWORD_PLAIN)
result->Transform(Key::KEY_TYPE_SALTED_SHA256_TOP_HALF, system_salt);
return result;
}
// Records get hash status and calls resolver->Resolve().
void TriggerResolveHash(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver,
bool success,
const std::string& username_hash) {
if (success)
attempt->RecordUsernameHash(username_hash);
else
attempt->RecordUsernameHashFailed();
resolver->Resolve();
}
// Records an error in accessing the user's cryptohome with the given key and
// calls resolver->Resolve() after adding a login time marker.
void RecordKeyErrorAndResolve(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver) {
chromeos::LoginEventRecorder::Get()->AddLoginTimeMarker("CryptohomeMount-End",
false);
attempt->RecordCryptohomeStatus(cryptohome::MOUNT_ERROR_KEY_FAILURE);
resolver->Resolve();
}
// Callback invoked when cryptohome's GetSantiziedUsername() method has
// finished.
void OnGetSanitizedUsername(
base::OnceCallback<void(bool, const std::string&)> callback,
base::Optional<std::string> result) {
std::move(callback).Run(result.has_value(), result.value_or(std::string()));
}
// Callback invoked when a crypotyhome *Ex method, which only returns a
// base::Reply, finishes.
void OnBaseReplyMethod(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver,
const std::string& time_marker,
base::Optional<cryptohome::BaseReply> reply) {
chromeos::LoginEventRecorder::Get()->AddLoginTimeMarker(time_marker, false);
attempt->RecordCryptohomeStatus(BaseReplyToMountError(reply));
resolver->Resolve();
}
// Callback invoked when cryptohome's MountEx() method has finished.
void OnMount(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver,
base::Optional<cryptohome::BaseReply> reply) {
const bool public_mount = attempt->user_context.GetUserType() ==
user_manager::USER_TYPE_KIOSK_APP ||
attempt->user_context.GetUserType() ==
user_manager::USER_TYPE_ARC_KIOSK_APP;
chromeos::LoginEventRecorder::Get()->AddLoginTimeMarker(
public_mount ? "CryptohomeMountPublic-End" : "CryptohomeMount-End",
false);
attempt->RecordCryptohomeStatus(MountExReplyToMountError(reply));
if (attempt->cryptohome_code() == cryptohome::MOUNT_ERROR_NONE) {
attempt->RecordUsernameHash(MountExReplyToMountHash(reply.value()));
} else {
LOGIN_LOG(ERROR) << "MountEx failed. Error: " << attempt->cryptohome_code();
attempt->RecordUsernameHashFailed();
}
resolver->Resolve();
}
// Calls cryptohome's MountEx() method. The key in |attempt->user_context| must
// not be a plain text password. If the user provided a plain text password,
// that password must be transformed to another key type (by salted hashing)
// before calling this method.
void DoMount(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver,
bool ephemeral,
bool create_if_nonexistent) {
const Key* key = attempt->user_context.GetKey();
// If the |key| is a plain text password, crash rather than attempting to
// mount the cryptohome with a plain text password.
CHECK_NE(Key::KEY_TYPE_PASSWORD_PLAIN, key->GetKeyType());
// Set state that username_hash is requested here so that test implementation
// that returns directly would not generate 2 OnLoginSucces() calls.
attempt->UsernameHashRequested();
cryptohome::MountRequest mount;
if (ephemeral)
mount.set_require_ephemeral(true);
if (create_if_nonexistent) {
cryptohome::KeyDefinitionToKey(
cryptohome::KeyDefinition::CreateForPassword(key->GetSecret(),
kCryptohomeGAIAKeyLabel,
cryptohome::PRIV_DEFAULT),
mount.mutable_create()->add_keys());
}
if (attempt->user_context.IsForcingDircrypto())
mount.set_force_dircrypto_if_available(true);
cryptohome::AuthorizationRequest auth;
cryptohome::Key* auth_key = auth.mutable_key();
// Don't set the authorization's key label, implicitly setting it to an
// empty string, which is a wildcard allowing any key to match. This is
// necessary because cryptohomes created by Chrome OS M38 and older will have
// a legacy key with no label while those created by Chrome OS M39 and newer
// will have a key with the label kCryptohomeGAIAKeyLabel.
//
// This logic does not apply to PIN and weak keys in general, as those do not
// authenticate against a wildcard label.
if (attempt->user_context.IsUsingPin())
auth_key->mutable_data()->set_label(key->GetLabel());
auth_key->set_secret(key->GetSecret());
DBusThreadManager::Get()->GetCryptohomeClient()->MountEx(
cryptohome::CreateAccountIdentifierFromAccountId(
attempt->user_context.GetAccountId()),
auth, mount, base::BindOnce(&OnMount, attempt, resolver));
}
// Handle cryptohome migration status.
void OnCryptohomeRenamed(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver,
bool ephemeral,
bool create_if_nonexistent,
base::Optional<cryptohome::BaseReply> reply) {
cryptohome::MountError return_code = cryptohome::BaseReplyToMountError(reply);
chromeos::LoginEventRecorder::Get()->AddLoginTimeMarker(
"CryptohomeRename-End", false);
const AccountId& account_id = attempt->user_context.GetAccountId();
if (return_code == cryptohome::MOUNT_ERROR_NONE) {
cryptohome::SetGaiaIdMigrationStatusDone(account_id);
UMACryptohomeMigrationToGaiaId(CryptohomeMigrationToGaiaId::SUCCESS);
} else {
LOG(ERROR) << "Failed to rename cryptohome for account of type "
<< AccountId::AccountTypeToString(account_id.GetAccountType())
<< " (return_code=" << return_code << ")";
// If rename fails, we can still use legacy cryptohome identifier.
// Proceed to DoMount.
UMACryptohomeMigrationToGaiaId(CryptohomeMigrationToGaiaId::FAILURE);
}
DoMount(attempt, resolver, ephemeral, create_if_nonexistent);
}
// This method migrates cryptohome identifier to gaia id (if needed),
// and then calls Mount.
void EnsureCryptohomeMigratedToGaiaId(
const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver,
bool ephemeral,
bool create_if_nonexistent) {
// Set the migration flag for Active Directory accounts since they're using
// the account id key as cryptohome id.
const AccountId& account_id = attempt->user_context.GetAccountId();
if (account_id.GetAccountType() == AccountType::ACTIVE_DIRECTORY) {
cryptohome::SetGaiaIdMigrationStatusDone(account_id);
}
// Only Google accounts have to be migrated.
if (account_id.GetAccountType() != AccountType::GOOGLE) {
DoMount(attempt, resolver, ephemeral, create_if_nonexistent);
return;
}
const bool is_gaiaid_migration_started = switches::IsGaiaIdMigrationStarted();
if (!is_gaiaid_migration_started) {
UMACryptohomeMigrationToGaiaId(CryptohomeMigrationToGaiaId::NOT_STARTED);
DoMount(attempt, resolver, ephemeral, create_if_nonexistent);
return;
}
const bool already_migrated =
cryptohome::GetGaiaIdMigrationStatus(account_id);
const bool has_account_key = account_id.HasAccountIdKey();
bool need_migration = false;
if (!create_if_nonexistent && !already_migrated) {
if (has_account_key) {
need_migration = true;
} else {
LOG(WARNING)
<< "Google account has no gaia id. Cryptohome migration skipped.";
}
}
if (need_migration) {
chromeos::LoginEventRecorder::Get()->AddLoginTimeMarker(
"CryptohomeRename-Start", false);
cryptohome::AccountIdentifier cryptohome_id_from;
cryptohome_id_from.set_account_id(account_id.GetUserEmail()); // Migrated
cryptohome::AccountIdentifier cryptohome_id_to;
cryptohome_id_to.set_account_id(account_id.GetAccountIdKey());
DBusThreadManager::Get()->GetCryptohomeClient()->RenameCryptohome(
cryptohome_id_from, cryptohome_id_to,
base::BindOnce(&OnCryptohomeRenamed, attempt, resolver, ephemeral,
create_if_nonexistent));
return;
}
if (!already_migrated && has_account_key) {
// Mark new users migrated.
cryptohome::SetGaiaIdMigrationStatusDone(account_id);
}
if (already_migrated) {
UMACryptohomeMigrationToGaiaId(
CryptohomeMigrationToGaiaId::ALREADY_MIGRATED);
}
DoMount(attempt, resolver, ephemeral, create_if_nonexistent);
}
// Callback invoked when the system salt has been retrieved. Transforms the key
// in |attempt->user_context| using Chrome's default hashing algorithm and the
// system salt, then calls MountEx().
void OnGetSystemSalt(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver,
bool ephemeral,
bool create_if_nonexistent,
const std::string& system_salt) {
DCHECK_EQ(Key::KEY_TYPE_PASSWORD_PLAIN,
attempt->user_context.GetKey()->GetKeyType());
attempt->user_context.GetKey()->Transform(
Key::KEY_TYPE_SALTED_SHA256_TOP_HALF, system_salt);
EnsureCryptohomeMigratedToGaiaId(attempt, resolver, ephemeral,
create_if_nonexistent);
}
// Callback invoked when cryptohome's GetKeyDataEx() method has finished.
// * If GetKeyDataEx() returned metadata indicating the hashing algorithm and
// salt that were used to generate the key for this user's cryptohome,
// transforms the key in |attempt->user_context| with the same parameters.
// * Otherwise, starts the retrieval of the system salt so that the key in
// |attempt->user_context| can be transformed with Chrome's default hashing
// algorithm and the system salt.
// The resulting key is then passed to cryptohome's MountEx().
void OnGetKeyDataEx(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver,
bool ephemeral,
bool create_if_nonexistent,
base::Optional<cryptohome::BaseReply> reply) {
if (cryptohome::GetKeyDataReplyToMountError(reply) ==
cryptohome::MOUNT_ERROR_NONE) {
std::vector<cryptohome::KeyDefinition> key_definitions =
cryptohome::GetKeyDataReplyToKeyDefinitions(reply);
if (key_definitions.size() == 1) {
const cryptohome::KeyDefinition& key_definition = key_definitions.front();
DCHECK_EQ(kCryptohomeGAIAKeyLabel, key_definition.label);
// Extract the key type and salt from |key_definition|, if present.
std::unique_ptr<int64_t> type;
std::unique_ptr<std::string> salt;
for (std::vector<cryptohome::KeyDefinition::ProviderData>::const_iterator
it = key_definition.provider_data.begin();
it != key_definition.provider_data.end(); ++it) {
if (it->name == kKeyProviderDataTypeName) {
if (it->number)
type.reset(new int64_t(*it->number));
else
NOTREACHED();
} else if (it->name == kKeyProviderDataSaltName) {
if (it->bytes)
salt.reset(new std::string(*it->bytes));
else
NOTREACHED();
}
}
if (type) {
if (*type < 0 || *type >= Key::KEY_TYPE_COUNT) {
LOGIN_LOG(ERROR) << "Invalid key type: " << *type;
RecordKeyErrorAndResolve(attempt, resolver);
return;
}
if (!salt) {
LOGIN_LOG(ERROR) << "Missing salt.";
RecordKeyErrorAndResolve(attempt, resolver);
return;
}
attempt->user_context.GetKey()->Transform(
static_cast<Key::KeyType>(*type), *salt);
EnsureCryptohomeMigratedToGaiaId(attempt, resolver, ephemeral,
create_if_nonexistent);
return;
}
} else {
LOGIN_LOG(EVENT) << "GetKeyDataEx() returned " << key_definitions.size()
<< " entries.";
}
}
SystemSaltGetter::Get()->GetSystemSalt(base::Bind(
&OnGetSystemSalt, attempt, resolver, ephemeral, create_if_nonexistent));
}
// Starts the process that will mount a user's cryptohome.
// * If the key in |attempt->user_context| is not a plain text password,
// cryptohome's MountEx() method is called directly with the key.
// * Otherwise, the key must be transformed (by salted hashing) before being
// passed to MountEx(). In that case, cryptohome's GetKeyDataEx() method is
// called to retrieve metadata indicating the hashing algorithm and salt that
// were used to generate the key for this user's cryptohome and the key is
// transformed accordingly before calling MountEx().
void StartMount(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver,
bool ephemeral,
bool create_if_nonexistent) {
chromeos::LoginEventRecorder::Get()->AddLoginTimeMarker(
"CryptohomeMount-Start", false);
if (attempt->user_context.GetKey()->GetKeyType() !=
Key::KEY_TYPE_PASSWORD_PLAIN) {
EnsureCryptohomeMigratedToGaiaId(attempt, resolver, ephemeral,
create_if_nonexistent);
return;
}
cryptohome::GetKeyDataRequest request;
request.mutable_key()->mutable_data()->set_label(kCryptohomeGAIAKeyLabel);
DBusThreadManager::Get()->GetCryptohomeClient()->GetKeyDataEx(
cryptohome::CreateAccountIdentifierFromAccountId(
attempt->user_context.GetAccountId()),
cryptohome::AuthorizationRequest(), request,
base::BindOnce(&OnGetKeyDataEx, attempt, resolver, ephemeral,
create_if_nonexistent));
}
// Calls cryptohome's mount method for guest and also get the user hash from
// cryptohome.
void MountGuestAndGetHash(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver) {
chromeos::LoginEventRecorder::Get()->AddLoginTimeMarker(
"CryptohomeMountGuest-Start", false);
attempt->UsernameHashRequested();
DBusThreadManager::Get()->GetCryptohomeClient()->MountGuestEx(
cryptohome::MountGuestRequest(),
base::BindOnce(&OnBaseReplyMethod, attempt, resolver,
"CryptohomeMountGuest-End"));
DBusThreadManager::Get()->GetCryptohomeClient()->GetSanitizedUsername(
cryptohome::CreateAccountIdentifierFromAccountId(
attempt->user_context.GetAccountId()),
base::BindOnce(&OnGetSanitizedUsername,
base::BindOnce(&TriggerResolveHash, attempt, resolver)));
}
// Calls cryptohome's MountEx method with the public_mount option.
void MountPublic(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver,
bool force_dircrypto_if_available) {
cryptohome::MountRequest mount;
if (force_dircrypto_if_available)
mount.set_force_dircrypto_if_available(true);
mount.set_public_mount(true);
// Set the request to create a new homedir when missing.
cryptohome::KeyDefinitionToKey(
cryptohome::KeyDefinition::CreateForPassword(
std::string(), kCryptohomePublicMountKeyLabel,
cryptohome::PRIV_DEFAULT),
mount.mutable_create()->add_keys());
// For public mounts, authorization secret is filled by cryptohomed, hence it
// is left empty. Authentication's key label is also set to an empty string,
// which is a wildcard allowing any key to match to allow cryptohomes created
// in a legacy way. (See comments in DoMount.)
DBusThreadManager::Get()->GetCryptohomeClient()->MountEx(
cryptohome::CreateAccountIdentifierFromAccountId(
attempt->user_context.GetAccountId()),
cryptohome::AuthorizationRequest(), mount,
base::BindOnce(&OnMount, attempt, resolver));
}
// Calls cryptohome's key migration method.
void Migrate(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver,
bool passing_old_hash,
const std::string& old_password,
const std::string& system_salt) {
chromeos::LoginEventRecorder::Get()->AddLoginTimeMarker(
"CryptohomeMigrate-Start", false);
cryptohome::AccountIdentifier account_id =
cryptohome::CreateAccountIdentifierFromAccountId(
attempt->user_context.GetAccountId());
cryptohome::AuthorizationRequest auth_request;
cryptohome::MigrateKeyRequest migrate_request;
// TODO(bartfab): Retrieve the hashing algorithm and salt to use for |old_key|
// from cryptohomed.
std::unique_ptr<Key> old_key =
TransformKeyIfNeeded(Key(old_password), system_salt);
std::unique_ptr<Key> new_key =
TransformKeyIfNeeded(*attempt->user_context.GetKey(), system_salt);
if (passing_old_hash) {
auth_request.mutable_key()->set_secret(old_key->GetSecret());
migrate_request.set_secret(new_key->GetSecret());
} else {
auth_request.mutable_key()->set_secret(new_key->GetSecret());
migrate_request.set_secret(old_key->GetSecret());
}
DBusThreadManager::Get()->GetCryptohomeClient()->MigrateKeyEx(
account_id, auth_request, migrate_request,
base::BindOnce(&OnBaseReplyMethod, attempt, resolver,
"CryptohomeMigrate-End"));
}
// Calls cryptohome's remove method.
void Remove(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver) {
chromeos::LoginEventRecorder::Get()->AddLoginTimeMarker(
"CryptohomeRemove-Start", false);
cryptohome::AccountIdentifier account_id;
account_id.set_account_id(
cryptohome::Identification(attempt->user_context.GetAccountId()).id());
DBusThreadManager::Get()->GetCryptohomeClient()->RemoveEx(
account_id, base::BindOnce(&OnBaseReplyMethod, attempt, resolver,
"CryptohomeRemove-End"));
}
void OnKeyChecked(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver,
base::Optional<cryptohome::BaseReply> reply) {
attempt->RecordCryptohomeStatus(BaseReplyToMountError(reply));
resolver->Resolve();
}
// Calls cryptohome's key check method.
void CheckKey(const base::WeakPtr<AuthAttemptState>& attempt,
scoped_refptr<CryptohomeAuthenticator> resolver,
const std::string& system_salt) {
std::unique_ptr<Key> key =
TransformKeyIfNeeded(*attempt->user_context.GetKey(), system_salt);
cryptohome::AuthorizationRequest auth;
auth.mutable_key()->set_secret(key->GetSecret());
DBusThreadManager::Get()->GetCryptohomeClient()->CheckKeyEx(
cryptohome::CreateAccountIdentifierFromAccountId(
attempt->user_context.GetAccountId()),
auth, cryptohome::CheckKeyRequest(),
base::BindOnce(&OnKeyChecked, attempt, resolver));
}
} // namespace
CryptohomeAuthenticator::CryptohomeAuthenticator(
scoped_refptr<base::TaskRunner> task_runner,
AuthStatusConsumer* consumer)
: Authenticator(consumer),
task_runner_(task_runner),
migrate_attempted_(false),
remove_attempted_(false),
resync_attempted_(false),
ephemeral_mount_attempted_(false),
check_key_attempted_(false),
already_reported_success_(false),
owner_is_verified_(false),
user_can_login_(false),
remove_user_data_on_failure_(false),
delayed_login_failure_(AuthFailure::NONE) {}
void CryptohomeAuthenticator::AuthenticateToLogin(
content::BrowserContext* context,
const UserContext& user_context) {
DCHECK(user_context.GetUserType() == user_manager::USER_TYPE_REGULAR ||
user_context.GetUserType() == user_manager::USER_TYPE_CHILD ||
user_context.GetUserType() ==
user_manager::USER_TYPE_ACTIVE_DIRECTORY);
authentication_context_ = context;
current_state_.reset(new AuthAttemptState(user_context,
false, // unlock
false, // online_complete
!IsKnownUser(user_context)));
// Reset the verified flag.
owner_is_verified_ = false;
StartMount(current_state_->AsWeakPtr(),
scoped_refptr<CryptohomeAuthenticator>(this),
false /* ephemeral */, false /* create_if_nonexistent */);
}
void CryptohomeAuthenticator::CompleteLogin(content::BrowserContext* context,
const UserContext& user_context) {
DCHECK(user_context.GetUserType() == user_manager::USER_TYPE_REGULAR ||
user_context.GetUserType() == user_manager::USER_TYPE_CHILD ||
user_context.GetUserType() ==
user_manager::USER_TYPE_ACTIVE_DIRECTORY);
authentication_context_ = context;
current_state_.reset(new AuthAttemptState(user_context,
true, // unlock
false, // online_complete
!IsKnownUser(user_context)));
// Reset the verified flag.
owner_is_verified_ = false;
if (!user_manager::known_user::FindPrefs(user_context.GetAccountId(),
nullptr)) {
// Save logged in user into local state as early as possible.
user_manager::known_user::SaveKnownUser(user_context.GetAccountId());
}
StartMount(current_state_->AsWeakPtr(),
scoped_refptr<CryptohomeAuthenticator>(this),
false /* ephemeral */, false /* create_if_nonexistent */);
// For login completion from extension, we just need to resolve the current
// auth attempt state, the rest of OAuth related tasks will be done in
// parallel.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CryptohomeAuthenticator::ResolveLoginCompletionStatus,
this));
}
void CryptohomeAuthenticator::AuthenticateToUnlock(
const UserContext& user_context) {
DCHECK_EQ(user_manager::USER_TYPE_REGULAR, user_context.GetUserType());
current_state_.reset(new AuthAttemptState(user_context,
true, // unlock
true, // online_complete
false)); // user_is_new
remove_user_data_on_failure_ = false;
check_key_attempted_ = true;
SystemSaltGetter::Get()->GetSystemSalt(
base::Bind(&CheckKey, current_state_->AsWeakPtr(),
scoped_refptr<CryptohomeAuthenticator>(this)));
}
void CryptohomeAuthenticator::LoginAsSupervisedUser(
const UserContext& user_context) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
DCHECK_EQ(user_manager::USER_TYPE_SUPERVISED, user_context.GetUserType());
// TODO(nkostylev): Pass proper value for |user_is_new| or remove (not used).
current_state_.reset(new AuthAttemptState(user_context,
false, // unlock
false, // online_complete
false)); // user_is_new
remove_user_data_on_failure_ = false;
StartMount(current_state_->AsWeakPtr(),
scoped_refptr<CryptohomeAuthenticator>(this),
false /* ephemeral */, false /* create_if_nonexistent */);
}
void CryptohomeAuthenticator::LoginOffTheRecord() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
current_state_.reset(
new AuthAttemptState(UserContext(user_manager::USER_TYPE_GUEST,
user_manager::GuestAccountId()),
false, // unlock
false, // online_complete
false)); // user_is_new
remove_user_data_on_failure_ = false;
ephemeral_mount_attempted_ = true;
MountGuestAndGetHash(current_state_->AsWeakPtr(),
scoped_refptr<CryptohomeAuthenticator>(this));
}
void CryptohomeAuthenticator::LoginAsPublicSession(
const UserContext& user_context) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
DCHECK_EQ(user_manager::USER_TYPE_PUBLIC_ACCOUNT, user_context.GetUserType());
current_state_.reset(new AuthAttemptState(user_context,
false, // unlock
false, // online_complete
false)); // user_is_new
remove_user_data_on_failure_ = false;
ephemeral_mount_attempted_ = true;
StartMount(current_state_->AsWeakPtr(),
scoped_refptr<CryptohomeAuthenticator>(this), true /* ephemeral */,
true /* create_if_nonexistent */);
}
void CryptohomeAuthenticator::LoginAsKioskAccount(
const AccountId& app_account_id,
bool use_guest_mount) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
const AccountId& account_id =
use_guest_mount ? user_manager::GuestAccountId() : app_account_id;
current_state_.reset(new AuthAttemptState(
UserContext(user_manager::USER_TYPE_KIOSK_APP, account_id),
false, // unlock
false, // online_complete
false)); // user_is_new
remove_user_data_on_failure_ = true;
if (!use_guest_mount) {
MountPublic(current_state_->AsWeakPtr(),
scoped_refptr<CryptohomeAuthenticator>(this),
false); // force_dircrypto_if_available
} else {
ephemeral_mount_attempted_ = true;
MountGuestAndGetHash(current_state_->AsWeakPtr(),
scoped_refptr<CryptohomeAuthenticator>(this));
}
}
void CryptohomeAuthenticator::LoginAsArcKioskAccount(
const AccountId& app_account_id) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
current_state_.reset(new AuthAttemptState(
UserContext(user_manager::USER_TYPE_ARC_KIOSK_APP, app_account_id),
false, // unlock
false, // online_complete
false)); // user_is_new
remove_user_data_on_failure_ = true;
MountPublic(current_state_->AsWeakPtr(),
scoped_refptr<CryptohomeAuthenticator>(this),
true); // force_dircrypto_if_available
}
void CryptohomeAuthenticator::OnAuthSuccess() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
VLOG(1) << "Login success";
// Send notification of success
chromeos::LoginEventRecorder::Get()->RecordAuthenticationSuccess();
{
base::AutoLock for_this_block(success_lock_);
already_reported_success_ = true;
}
if (consumer_)
consumer_->OnAuthSuccess(current_state_->user_context);
}
void CryptohomeAuthenticator::OnOffTheRecordAuthSuccess() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
chromeos::LoginEventRecorder::Get()->RecordAuthenticationSuccess();
if (consumer_)
consumer_->OnOffTheRecordAuthSuccess();
}
void CryptohomeAuthenticator::OnPasswordChangeDetected() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (consumer_)
consumer_->OnPasswordChangeDetected();
}
void CryptohomeAuthenticator::OnOldEncryptionDetected(
bool has_incomplete_migration) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (consumer_) {
consumer_->OnOldEncryptionDetected(current_state_->user_context,
has_incomplete_migration);
}
}
void CryptohomeAuthenticator::OnAuthFailure(const AuthFailure& error) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
// OnAuthFailure will be called again with the same |error|
// after the cryptohome has been removed.
if (remove_user_data_on_failure_) {
delayed_login_failure_ = error;
RemoveEncryptedData();
return;
}
chromeos::LoginEventRecorder::Get()->RecordAuthenticationFailure();
LOGIN_LOG(ERROR) << "Login failed: " << error.GetErrorString();
if (consumer_)
consumer_->OnAuthFailure(error);
}
void CryptohomeAuthenticator::RecoverEncryptedData(
const std::string& old_password) {
migrate_attempted_ = true;
current_state_->ResetCryptohomeStatus();
SystemSaltGetter::Get()->GetSystemSalt(base::Bind(
&Migrate, current_state_->AsWeakPtr(),
scoped_refptr<CryptohomeAuthenticator>(this), true, old_password));
}
void CryptohomeAuthenticator::RemoveEncryptedData() {
remove_attempted_ = true;
current_state_->ResetCryptohomeStatus();
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Remove, current_state_->AsWeakPtr(),
scoped_refptr<CryptohomeAuthenticator>(this)));
}
void CryptohomeAuthenticator::ResyncEncryptedData() {
resync_attempted_ = true;
current_state_->ResetCryptohomeStatus();
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Remove, current_state_->AsWeakPtr(),
scoped_refptr<CryptohomeAuthenticator>(this)));
}
bool CryptohomeAuthenticator::VerifyOwner() {
if (owner_is_verified_)
return true;
// Check if policy data is fine and continue in safe mode if needed.
if (!IsSafeMode()) {
// Now we can continue with the login and report mount success.
user_can_login_ = true;
owner_is_verified_ = true;
return true;
}
CheckSafeModeOwnership(
current_state_->user_context,
base::Bind(&CryptohomeAuthenticator::OnOwnershipChecked, this));
return false;
}
void CryptohomeAuthenticator::OnOwnershipChecked(bool is_owner) {
// Now we can check if this user is the owner.
user_can_login_ = is_owner;
owner_is_verified_ = true;
Resolve();
}
void CryptohomeAuthenticator::OnUnmount(base::Optional<bool> success) {
if (!success.has_value() || !success.value()) {
// Maybe we should reboot immediately here?
LOGIN_LOG(ERROR) << "Couldn't unmount users home!";
}
OnAuthFailure(AuthFailure(AuthFailure::OWNER_REQUIRED));
}
void CryptohomeAuthenticator::Resolve() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
bool create_if_nonexistent = false;
CryptohomeAuthenticator::AuthState state = ResolveState();
VLOG(1) << "Resolved state to " << state << " (" << AuthStateToString(state)
<< ")";
switch (state) {
case CONTINUE:
case POSSIBLE_PW_CHANGE:
case NO_MOUNT:
// These are intermediate states; we need more info from a request that
// is still pending.
break;
case FAILED_MOUNT:
// In this case, whether login succeeded or not, we can't log
// the user in because their data is horked. So, override with
// the appropriate failure.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CryptohomeAuthenticator::OnAuthFailure, this,
AuthFailure(AuthFailure::COULD_NOT_MOUNT_CRYPTOHOME)));
break;
case FAILED_REMOVE:
// In this case, we tried to remove the user's old cryptohome at their
// request, and the remove failed.
remove_user_data_on_failure_ = false;
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CryptohomeAuthenticator::OnAuthFailure, this,
AuthFailure(AuthFailure::DATA_REMOVAL_FAILED)));
break;
case FAILED_TMPFS:
// In this case, we tried to mount a tmpfs for guest and failed.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CryptohomeAuthenticator::OnAuthFailure, this,
AuthFailure(AuthFailure::COULD_NOT_MOUNT_TMPFS)));
break;
case FAILED_TPM:
// In this case, we tried to create/mount cryptohome and failed
// because of the critical TPM error.
// Chrome will notify user and request reboot.
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CryptohomeAuthenticator::OnAuthFailure,
this, AuthFailure(AuthFailure::TPM_ERROR)));
break;
case FAILED_USERNAME_HASH:
// In this case, we failed the GetSanitizedUsername request to
// cryptohomed. This can happen for any login attempt.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CryptohomeAuthenticator::OnAuthFailure, this,
AuthFailure(AuthFailure::USERNAME_HASH_FAILED)));
break;
case REMOVED_DATA_AFTER_FAILURE:
remove_user_data_on_failure_ = false;
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CryptohomeAuthenticator::OnAuthFailure,
this, delayed_login_failure_));
break;
case CREATE_NEW:
create_if_nonexistent = true;
FALLTHROUGH;
case RECOVER_MOUNT:
current_state_->ResetCryptohomeStatus();
StartMount(current_state_->AsWeakPtr(),
scoped_refptr<CryptohomeAuthenticator>(this),
false /*ephemeral*/, create_if_nonexistent);
break;
case NEED_OLD_PW:
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CryptohomeAuthenticator::OnPasswordChangeDetected,
this));
break;
case ONLINE_FAILED:
case NEED_NEW_PW:
case HAVE_NEW_PW:
NOTREACHED() << "Using obsolete ClientLogin code path.";
break;
case OFFLINE_LOGIN:
VLOG(2) << "Offline login";
FALLTHROUGH;
case UNLOCK:
VLOG(2) << "Unlock";
FALLTHROUGH;
case ONLINE_LOGIN:
VLOG(2) << "Online login";
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CryptohomeAuthenticator::OnAuthSuccess, this));
break;
case GUEST_LOGIN:
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CryptohomeAuthenticator::OnOffTheRecordAuthSuccess,
this));
break;
case KIOSK_ACCOUNT_LOGIN:
case PUBLIC_ACCOUNT_LOGIN:
current_state_->user_context.SetIsUsingOAuth(false);
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CryptohomeAuthenticator::OnAuthSuccess, this));
break;
case SUPERVISED_USER_LOGIN:
current_state_->user_context.SetIsUsingOAuth(false);
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CryptohomeAuthenticator::OnAuthSuccess, this));
break;
case LOGIN_FAILED:
current_state_->ResetCryptohomeStatus();
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CryptohomeAuthenticator::OnAuthFailure,
this, current_state_->online_outcome()));
break;
case OWNER_REQUIRED: {
current_state_->ResetCryptohomeStatus();
DBusThreadManager::Get()->GetCryptohomeClient()->Unmount(
base::BindOnce(&CryptohomeAuthenticator::OnUnmount, this));
break;
}
case FAILED_OLD_ENCRYPTION:
case FAILED_PREVIOUS_MIGRATION_INCOMPLETE:
// In this case, we tried to create/mount cryptohome and failed
// because the file system is encrypted in old format.
// Chrome will show a screen which asks user to migrate the encryption.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CryptohomeAuthenticator::OnOldEncryptionDetected,
this, state == FAILED_PREVIOUS_MIGRATION_INCOMPLETE));
break;
case OFFLINE_NO_MOUNT:
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CryptohomeAuthenticator::OnAuthFailure, this,
AuthFailure(AuthFailure::MISSING_CRYPTOHOME)));
break;
default:
NOTREACHED();
break;
}
}
CryptohomeAuthenticator::~CryptohomeAuthenticator() = default;
CryptohomeAuthenticator::AuthState CryptohomeAuthenticator::ResolveState() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
// If we haven't mounted the user's home dir yet or
// haven't got sanitized username value, we can't be done.
// We never get past here if any of these two cryptohome ops is still pending.
// This is an important invariant.
if (!current_state_->cryptohome_complete() ||
!current_state_->username_hash_obtained()) {
return CONTINUE;
}
AuthState state = CONTINUE;
if (current_state_->cryptohome_code() == cryptohome::MOUNT_ERROR_NONE &&
current_state_->username_hash_valid()) {
state = ResolveCryptohomeSuccessState();
} else {
state = ResolveCryptohomeFailureState();
LOGIN_LOG(ERROR) << "Cryptohome failure: "
<< "state(AuthState)=" << state
<< ", code(cryptohome::MountError)="
<< current_state_->cryptohome_code();
}
DCHECK(current_state_->cryptohome_complete()); // Ensure invariant holds.
migrate_attempted_ = false;
remove_attempted_ = false;
resync_attempted_ = false;
ephemeral_mount_attempted_ = false;
check_key_attempted_ = false;
if (state != POSSIBLE_PW_CHANGE && state != NO_MOUNT &&
state != OFFLINE_LOGIN)
return state;
if (current_state_->online_complete()) {
if (current_state_->online_outcome().reason() == AuthFailure::NONE) {
// Online attempt succeeded as well, so combine the results.
return ResolveOnlineSuccessState(state);
}
NOTREACHED() << "Using obsolete ClientLogin code path.";
}
// if online isn't complete yet, just return the offline result.
return state;
}
CryptohomeAuthenticator::AuthState
CryptohomeAuthenticator::ResolveCryptohomeFailureState() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (remove_attempted_ || resync_attempted_)
return FAILED_REMOVE;
if (ephemeral_mount_attempted_)
return FAILED_TMPFS;
if (migrate_attempted_)
return NEED_OLD_PW;
if (check_key_attempted_)
return LOGIN_FAILED;
if (current_state_->cryptohome_code() ==
cryptohome::MOUNT_ERROR_TPM_NEEDS_REBOOT) {
// Critical TPM error detected, reboot needed.
return FAILED_TPM;
}
if (current_state_->cryptohome_code() ==
cryptohome::MOUNT_ERROR_OLD_ENCRYPTION) {
return FAILED_OLD_ENCRYPTION;
}
if (current_state_->cryptohome_code() ==
cryptohome::MOUNT_ERROR_PREVIOUS_MIGRATION_INCOMPLETE) {
return FAILED_PREVIOUS_MIGRATION_INCOMPLETE;
}
// Return intermediate states in the following case:
// when there is an online result to use;
// This is the case after user finishes Gaia login;
if (current_state_->online_complete()) {
if (current_state_->cryptohome_code() ==
cryptohome::MOUNT_ERROR_KEY_FAILURE) {
// If we tried a mount but they used the wrong key, we may need to
// ask the user for their old password. We'll only know once we've
// done the online check.
return POSSIBLE_PW_CHANGE;
}
if (current_state_->cryptohome_code() ==
cryptohome::MOUNT_ERROR_USER_DOES_NOT_EXIST) {
// If we tried a mount but the user did not exist, then we should wait
// for online login to succeed and try again with the "create" flag set.
return NO_MOUNT;
}
} else if (current_state_->cryptohome_code() ==
cryptohome::MOUNT_ERROR_USER_DOES_NOT_EXIST) {
// If we tried a mount but the user did not exist in the offline flow,
// surface this as an error.
return OFFLINE_NO_MOUNT;
}
if (!current_state_->username_hash_valid())
return FAILED_USERNAME_HASH;
return FAILED_MOUNT;
}
CryptohomeAuthenticator::AuthState
CryptohomeAuthenticator::ResolveCryptohomeSuccessState() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (resync_attempted_)
return CREATE_NEW;
if (remove_attempted_)
return REMOVED_DATA_AFTER_FAILURE;
if (migrate_attempted_)
return RECOVER_MOUNT;
if (check_key_attempted_)
return UNLOCK;
const user_manager::UserType user_type =
current_state_->user_context.GetUserType();
if (user_type == user_manager::USER_TYPE_GUEST)
return GUEST_LOGIN;
if (user_type == user_manager::USER_TYPE_PUBLIC_ACCOUNT)
return PUBLIC_ACCOUNT_LOGIN;
if (user_type == user_manager::USER_TYPE_KIOSK_APP)
return KIOSK_ACCOUNT_LOGIN;
if (user_type == user_manager::USER_TYPE_SUPERVISED)
return SUPERVISED_USER_LOGIN;
if (!VerifyOwner())
return CONTINUE;
return user_can_login_ ? OFFLINE_LOGIN : OWNER_REQUIRED;
}
CryptohomeAuthenticator::AuthState
CryptohomeAuthenticator::ResolveOnlineSuccessState(
CryptohomeAuthenticator::AuthState offline_state) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
switch (offline_state) {
case POSSIBLE_PW_CHANGE:
return NEED_OLD_PW;
case NO_MOUNT:
return CREATE_NEW;
case OFFLINE_LOGIN:
return ONLINE_LOGIN;
default:
NOTREACHED();
return offline_state;
}
}
void CryptohomeAuthenticator::ResolveLoginCompletionStatus() {
// Shortcut online state resolution process.
current_state_->RecordOnlineLoginStatus(AuthFailure::AuthFailureNone());
Resolve();
}
void CryptohomeAuthenticator::SetOwnerState(bool owner_check_finished,
bool check_result) {
owner_is_verified_ = owner_check_finished;
user_can_login_ = check_result;
}
} // namespace chromeos