diff options
author | antrim@chromium.org <antrim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-08-21 17:29:56 +0000 |
---|---|---|
committer | antrim@chromium.org <antrim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-08-21 17:31:24 +0000 |
commit | 2bb3ea82ec57a7e44cddb3b596d23aa934920df2 (patch) | |
tree | 9fbeacfeae5b3965d8b56413dd8f138bc045475e /chromeos | |
parent | ada965b23e1b32f3faef25d2bca5c509c12d8a97 (diff) | |
download | chromium_src-2bb3ea82ec57a7e44cddb3b596d23aa934920df2.zip chromium_src-2bb3ea82ec57a7e44cddb3b596d23aa934920df2.tar.gz chromium_src-2bb3ea82ec57a7e44cddb3b596d23aa934920df2.tar.bz2 |
Move ParallelAuthnticator to chromeos and rename it to CryptohomeAuthenticator
BUG=387613
R=nkostylev@chromium.org
TBR=stevenjb@chromium.org
Review URL: https://codereview.chromium.org/495563003
Cr-Commit-Position: refs/heads/master@{#291125}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@291125 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chromeos')
-rw-r--r-- | chromeos/chromeos.gyp | 2 | ||||
-rw-r--r-- | chromeos/login/auth/cryptohome_authenticator.cc | 757 | ||||
-rw-r--r-- | chromeos/login/auth/cryptohome_authenticator.h | 269 |
3 files changed, 1028 insertions, 0 deletions
diff --git a/chromeos/chromeos.gyp b/chromeos/chromeos.gyp index bd96865..51cb17a 100644 --- a/chromeos/chromeos.gyp +++ b/chromeos/chromeos.gyp @@ -279,6 +279,8 @@ 'login/auth/auth_status_consumer.h', 'login/auth/authenticator.cc', 'login/auth/authenticator.h', + 'login/auth/cryptohome_authenticator.cc', + 'login/auth/cryptohome_authenticator.h', 'login/auth/extended_authenticator.cc', 'login/auth/extended_authenticator.h', 'login/auth/key.cc', diff --git a/chromeos/login/auth/cryptohome_authenticator.cc b/chromeos/login/auth/cryptohome_authenticator.cc new file mode 100644 index 0000000..91dc5f7 --- /dev/null +++ b/chromeos/login/auth/cryptohome_authenticator.cc @@ -0,0 +1,757 @@ +// 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 "base/bind.h" +#include "base/files/file_path.h" +#include "base/location.h" +#include "base/logging.h" +#include "chromeos/cryptohome/async_method_caller.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/user_names.h" +#include "chromeos/login_event_recorder.h" +#include "components/user_manager/user_type.h" +#include "third_party/cros_system_api/dbus/service_constants.h" + +namespace chromeos { + +namespace { + +// Hashes |key| with |system_salt| if it its type is KEY_TYPE_PASSWORD_PLAIN. +// Returns the keys unmodified otherwise. +scoped_ptr<Key> TransformKeyIfNeeded(const Key& key, + const std::string& system_salt) { + scoped_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.Pass(); +} + +// Records status and calls resolver->Resolve(). +void TriggerResolve(AuthAttemptState* attempt, + scoped_refptr<CryptohomeAuthenticator> resolver, + bool success, + cryptohome::MountError return_code) { + attempt->RecordCryptohomeStatus(success, return_code); + resolver->Resolve(); +} + +// Records get hash status and calls resolver->Resolve(). +void TriggerResolveHash(AuthAttemptState* attempt, + scoped_refptr<CryptohomeAuthenticator> resolver, + bool success, + const std::string& username_hash) { + if (success) + attempt->RecordUsernameHash(username_hash); + else + attempt->RecordUsernameHashFailed(); + resolver->Resolve(); +} + +// Calls TriggerResolve while adding login time marker. +void TriggerResolveWithLoginTimeMarker( + const std::string& marker_name, + AuthAttemptState* attempt, + scoped_refptr<CryptohomeAuthenticator> resolver, + bool success, + cryptohome::MountError return_code) { + chromeos::LoginEventRecorder::Get()->AddLoginTimeMarker(marker_name, false); + TriggerResolve(attempt, resolver, success, return_code); +} + +// Calls cryptohome's mount method. +void Mount(AuthAttemptState* attempt, + scoped_refptr<CryptohomeAuthenticator> resolver, + int flags, + const std::string& system_salt) { + chromeos::LoginEventRecorder::Get()->AddLoginTimeMarker( + "CryptohomeMount-Start", false); + // Set state that username_hash is requested here so that test implementation + // that returns directly would not generate 2 OnLoginSucces() calls. + attempt->UsernameHashRequested(); + + scoped_ptr<Key> key = + TransformKeyIfNeeded(*attempt->user_context.GetKey(), system_salt); + cryptohome::AsyncMethodCaller::GetInstance()->AsyncMount( + attempt->user_context.GetUserID(), + key->GetSecret(), + flags, + base::Bind(&TriggerResolveWithLoginTimeMarker, + "CryptohomeMount-End", + attempt, + resolver)); + cryptohome::AsyncMethodCaller::GetInstance()->AsyncGetSanitizedUsername( + attempt->user_context.GetUserID(), + base::Bind(&TriggerResolveHash, attempt, resolver)); +} + +// Calls cryptohome's mount method for guest and also get the user hash from +// cryptohome. +void MountGuestAndGetHash(AuthAttemptState* attempt, + scoped_refptr<CryptohomeAuthenticator> resolver) { + attempt->UsernameHashRequested(); + cryptohome::AsyncMethodCaller::GetInstance()->AsyncMountGuest( + base::Bind(&TriggerResolveWithLoginTimeMarker, + "CryptohomeMount-End", + attempt, + resolver)); + cryptohome::AsyncMethodCaller::GetInstance()->AsyncGetSanitizedUsername( + attempt->user_context.GetUserID(), + base::Bind(&TriggerResolveHash, attempt, resolver)); +} + +// Calls cryptohome's MountPublic method +void MountPublic(AuthAttemptState* attempt, + scoped_refptr<CryptohomeAuthenticator> resolver, + int flags) { + cryptohome::AsyncMethodCaller::GetInstance()->AsyncMountPublic( + attempt->user_context.GetUserID(), + flags, + base::Bind(&TriggerResolveWithLoginTimeMarker, + "CryptohomeMountPublic-End", + attempt, + resolver)); + cryptohome::AsyncMethodCaller::GetInstance()->AsyncGetSanitizedUsername( + attempt->user_context.GetUserID(), + base::Bind(&TriggerResolveHash, attempt, resolver)); +} + +// Calls cryptohome's key migration method. +void Migrate(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::AsyncMethodCaller* caller = + cryptohome::AsyncMethodCaller::GetInstance(); + + // TODO(bartfab): Retrieve the hashing algorithm and salt to use for |old_key| + // from cryptohomed. + scoped_ptr<Key> old_key = + TransformKeyIfNeeded(Key(old_password), system_salt); + scoped_ptr<Key> new_key = + TransformKeyIfNeeded(*attempt->user_context.GetKey(), system_salt); + if (passing_old_hash) { + caller->AsyncMigrateKey(attempt->user_context.GetUserID(), + old_key->GetSecret(), + new_key->GetSecret(), + base::Bind(&TriggerResolveWithLoginTimeMarker, + "CryptohomeMount-End", + attempt, + resolver)); + } else { + caller->AsyncMigrateKey(attempt->user_context.GetUserID(), + new_key->GetSecret(), + old_key->GetSecret(), + base::Bind(&TriggerResolveWithLoginTimeMarker, + "CryptohomeMount-End", + attempt, + resolver)); + } +} + +// Calls cryptohome's remove method. +void Remove(AuthAttemptState* attempt, + scoped_refptr<CryptohomeAuthenticator> resolver) { + chromeos::LoginEventRecorder::Get()->AddLoginTimeMarker( + "CryptohomeRemove-Start", false); + cryptohome::AsyncMethodCaller::GetInstance()->AsyncRemove( + attempt->user_context.GetUserID(), + base::Bind(&TriggerResolveWithLoginTimeMarker, + "CryptohomeRemove-End", + attempt, + resolver)); +} + +// Calls cryptohome's key check method. +void CheckKey(AuthAttemptState* attempt, + scoped_refptr<CryptohomeAuthenticator> resolver, + const std::string& system_salt) { + scoped_ptr<Key> key = + TransformKeyIfNeeded(*attempt->user_context.GetKey(), system_salt); + cryptohome::AsyncMethodCaller::GetInstance()->AsyncCheckKey( + attempt->user_context.GetUserID(), + key->GetSecret(), + base::Bind(&TriggerResolve, 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_(NULL) { +} + +void CryptohomeAuthenticator::AuthenticateToLogin( + Profile* profile, + const UserContext& user_context) { + authentication_profile_ = profile; + current_state_.reset(new AuthAttemptState(user_context, + user_manager::USER_TYPE_REGULAR, + false, // unlock + false, // online_complete + !IsKnownUser(user_context))); + // Reset the verified flag. + owner_is_verified_ = false; + + SystemSaltGetter::Get()->GetSystemSalt( + base::Bind(&Mount, + current_state_.get(), + scoped_refptr<CryptohomeAuthenticator>(this), + cryptohome::MOUNT_FLAGS_NONE)); +} + +void CryptohomeAuthenticator::CompleteLogin(Profile* profile, + const UserContext& user_context) { + authentication_profile_ = profile; + current_state_.reset(new AuthAttemptState(user_context, + user_manager::USER_TYPE_REGULAR, + true, // unlock + false, // online_complete + !IsKnownUser(user_context))); + + // Reset the verified flag. + owner_is_verified_ = false; + + SystemSaltGetter::Get()->GetSystemSalt( + base::Bind(&Mount, + current_state_.get(), + scoped_refptr<CryptohomeAuthenticator>(this), + cryptohome::MOUNT_FLAGS_NONE)); + + // 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::Bind(&CryptohomeAuthenticator::ResolveLoginCompletionStatus, this)); +} + +void CryptohomeAuthenticator::AuthenticateToUnlock( + const UserContext& user_context) { + current_state_.reset(new AuthAttemptState(user_context, + user_manager::USER_TYPE_REGULAR, + 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_.get(), + scoped_refptr<CryptohomeAuthenticator>(this))); +} + +void CryptohomeAuthenticator::LoginAsSupervisedUser( + const UserContext& user_context) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + // TODO(nkostylev): Pass proper value for |user_is_new| or remove (not used). + current_state_.reset(new AuthAttemptState(user_context, + user_manager::USER_TYPE_SUPERVISED, + false, // unlock + false, // online_complete + false)); // user_is_new + remove_user_data_on_failure_ = false; + SystemSaltGetter::Get()->GetSystemSalt( + base::Bind(&Mount, + current_state_.get(), + scoped_refptr<CryptohomeAuthenticator>(this), + cryptohome::MOUNT_FLAGS_NONE)); +} + +void CryptohomeAuthenticator::LoginRetailMode() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + // Note: |kRetailModeUserEMail| is used in other places to identify a retail + // mode session. + current_state_.reset( + new AuthAttemptState(UserContext(chromeos::login::kRetailModeUserName), + user_manager::USER_TYPE_RETAIL_MODE, + false, // unlock + false, // online_complete + false)); // user_is_new + remove_user_data_on_failure_ = false; + ephemeral_mount_attempted_ = true; + MountGuestAndGetHash(current_state_.get(), + scoped_refptr<CryptohomeAuthenticator>(this)); +} + +void CryptohomeAuthenticator::LoginOffTheRecord() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + current_state_.reset( + new AuthAttemptState(UserContext(chromeos::login::kGuestUserName), + user_manager::USER_TYPE_GUEST, + false, // unlock + false, // online_complete + false)); // user_is_new + remove_user_data_on_failure_ = false; + ephemeral_mount_attempted_ = true; + MountGuestAndGetHash(current_state_.get(), + scoped_refptr<CryptohomeAuthenticator>(this)); +} + +void CryptohomeAuthenticator::LoginAsPublicSession( + const UserContext& user_context) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + current_state_.reset( + new AuthAttemptState(user_context, + user_manager::USER_TYPE_PUBLIC_ACCOUNT, + false, // unlock + false, // online_complete + false)); // user_is_new + remove_user_data_on_failure_ = false; + ephemeral_mount_attempted_ = true; + SystemSaltGetter::Get()->GetSystemSalt( + base::Bind(&Mount, + current_state_.get(), + scoped_refptr<CryptohomeAuthenticator>(this), + cryptohome::CREATE_IF_MISSING | cryptohome::ENSURE_EPHEMERAL)); +} + +void CryptohomeAuthenticator::LoginAsKioskAccount( + const std::string& app_user_id, + bool use_guest_mount) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + + const std::string user_id = + use_guest_mount ? chromeos::login::kGuestUserName : app_user_id; + current_state_.reset(new AuthAttemptState(UserContext(user_id), + user_manager::USER_TYPE_KIOSK_APP, + false, // unlock + false, // online_complete + false)); // user_is_new + + remove_user_data_on_failure_ = true; + if (!use_guest_mount) { + MountPublic(current_state_.get(), + scoped_refptr<CryptohomeAuthenticator>(this), + cryptohome::CREATE_IF_MISSING); + } else { + ephemeral_mount_attempted_ = true; + MountGuestAndGetHash(current_state_.get(), + scoped_refptr<CryptohomeAuthenticator>(this)); + } +} + +void CryptohomeAuthenticator::OnRetailModeAuthSuccess() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + VLOG(1) << "Retail mode login success"; + chromeos::LoginEventRecorder::Get()->RecordAuthenticationSuccess(); + if (consumer_) + consumer_->OnRetailModeAuthSuccess(current_state_->user_context); +} + +void CryptohomeAuthenticator::OnAuthSuccess() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + 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_->RunsTasksOnCurrentThread()); + chromeos::LoginEventRecorder::Get()->RecordAuthenticationSuccess(); + if (consumer_) + consumer_->OnOffTheRecordAuthSuccess(); +} + +void CryptohomeAuthenticator::OnPasswordChangeDetected() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + if (consumer_) + consumer_->OnPasswordChangeDetected(); +} + +void CryptohomeAuthenticator::OnAuthFailure(const AuthFailure& error) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + + // 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(); + LOG(WARNING) << "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_.get(), + scoped_refptr<CryptohomeAuthenticator>(this), + true, + old_password)); +} + +void CryptohomeAuthenticator::RemoveEncryptedData() { + remove_attempted_ = true; + current_state_->ResetCryptohomeStatus(); + task_runner_->PostTask( + FROM_HERE, + base::Bind(&Remove, + current_state_.get(), + scoped_refptr<CryptohomeAuthenticator>(this))); +} + +void CryptohomeAuthenticator::ResyncEncryptedData() { + resync_attempted_ = true; + current_state_->ResetCryptohomeStatus(); + task_runner_->PostTask( + FROM_HERE, + base::Bind(&Remove, + current_state_.get(), + 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::Resolve() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + int mount_flags = cryptohome::MOUNT_FLAGS_NONE; + CryptohomeAuthenticator::AuthState state = ResolveState(); + VLOG(1) << "Resolved state to: " << 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::Bind(&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 her + // request, and the remove failed. + remove_user_data_on_failure_ = false; + task_runner_->PostTask( + FROM_HERE, + base::Bind(&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::Bind(&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::Bind(&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::Bind(&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::Bind(&CryptohomeAuthenticator::OnAuthFailure, + this, + *delayed_login_failure_)); + break; + case CREATE_NEW: + mount_flags |= cryptohome::CREATE_IF_MISSING; + case RECOVER_MOUNT: + current_state_->ResetCryptohomeStatus(); + SystemSaltGetter::Get()->GetSystemSalt( + base::Bind(&Mount, + current_state_.get(), + scoped_refptr<CryptohomeAuthenticator>(this), + mount_flags)); + break; + case NEED_OLD_PW: + task_runner_->PostTask( + FROM_HERE, + base::Bind(&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"; + // Fall through. + case UNLOCK: + VLOG(2) << "Unlock"; + // Fall through. + case ONLINE_LOGIN: + VLOG(2) << "Online login"; + task_runner_->PostTask( + FROM_HERE, base::Bind(&CryptohomeAuthenticator::OnAuthSuccess, this)); + break; + case DEMO_LOGIN: + VLOG(2) << "Retail mode login"; + current_state_->user_context.SetIsUsingOAuth(false); + task_runner_->PostTask( + FROM_HERE, + base::Bind(&CryptohomeAuthenticator::OnRetailModeAuthSuccess, this)); + break; + case GUEST_LOGIN: + task_runner_->PostTask( + FROM_HERE, + base::Bind(&CryptohomeAuthenticator::OnOffTheRecordAuthSuccess, + this)); + break; + case KIOSK_ACCOUNT_LOGIN: + case PUBLIC_ACCOUNT_LOGIN: + current_state_->user_context.SetIsUsingOAuth(false); + task_runner_->PostTask( + FROM_HERE, base::Bind(&CryptohomeAuthenticator::OnAuthSuccess, this)); + break; + case SUPERVISED_USER_LOGIN: + current_state_->user_context.SetIsUsingOAuth(false); + task_runner_->PostTask( + FROM_HERE, base::Bind(&CryptohomeAuthenticator::OnAuthSuccess, this)); + break; + case LOGIN_FAILED: + current_state_->ResetCryptohomeStatus(); + task_runner_->PostTask(FROM_HERE, + base::Bind(&CryptohomeAuthenticator::OnAuthFailure, + this, + current_state_->online_outcome())); + break; + case OWNER_REQUIRED: { + current_state_->ResetCryptohomeStatus(); + bool success = false; + DBusThreadManager::Get()->GetCryptohomeClient()->Unmount(&success); + if (!success) { + // Maybe we should reboot immediately here? + LOG(ERROR) << "Couldn't unmount users home!"; + } + task_runner_->PostTask( + FROM_HERE, + base::Bind(&CryptohomeAuthenticator::OnAuthFailure, + this, + AuthFailure(AuthFailure::OWNER_REQUIRED))); + break; + } + default: + NOTREACHED(); + break; + } +} + +CryptohomeAuthenticator::~CryptohomeAuthenticator() { +} + +CryptohomeAuthenticator::AuthState CryptohomeAuthenticator::ResolveState() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + // 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_outcome() && + current_state_->username_hash_valid()) { + state = ResolveCryptohomeSuccessState(); + } else { + state = ResolveCryptohomeFailureState(); + } + + 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_->RunsTasksOnCurrentThread()); + 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; + } + + // 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 her 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; + } + } + + if (!current_state_->username_hash_valid()) + return FAILED_USERNAME_HASH; + + return FAILED_MOUNT; +} + +CryptohomeAuthenticator::AuthState +CryptohomeAuthenticator::ResolveCryptohomeSuccessState() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + 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; + + if (current_state_->user_type == user_manager::USER_TYPE_GUEST) + return GUEST_LOGIN; + if (current_state_->user_type == user_manager::USER_TYPE_RETAIL_MODE) + return DEMO_LOGIN; + if (current_state_->user_type == user_manager::USER_TYPE_PUBLIC_ACCOUNT) + return PUBLIC_ACCOUNT_LOGIN; + if (current_state_->user_type == user_manager::USER_TYPE_KIOSK_APP) + return KIOSK_ACCOUNT_LOGIN; + if (current_state_->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_->RunsTasksOnCurrentThread()); + 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 diff --git a/chromeos/login/auth/cryptohome_authenticator.h b/chromeos/login/auth/cryptohome_authenticator.h new file mode 100644 index 0000000..d003b83 --- /dev/null +++ b/chromeos/login/auth/cryptohome_authenticator.h @@ -0,0 +1,269 @@ +// 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. + +#ifndef CHROMEOS_LOGIN_AUTH_CRYPTOHOME_AUTHENTICATOR_H_ +#define CHROMEOS_LOGIN_AUTH_CRYPTOHOME_AUTHENTICATOR_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "chromeos/chromeos_export.h" +#include "chromeos/login/auth/auth_attempt_state.h" +#include "chromeos/login/auth/auth_attempt_state_resolver.h" +#include "chromeos/login/auth/authenticator.h" +#include "chromeos/login/auth/test_attempt_state.h" +#include "google_apis/gaia/gaia_auth_consumer.h" + +class AuthFailure; +class Profile; + +namespace chromeos { + +class AuthStatusConsumer; + +// Authenticates a Chromium OS user against cryptohome. +// Relies on the fact that online authentications has been already performed +// (i.e. using_oauth_ is true). +// +// At a high, level, here's what happens: +// AuthenticateToLogin() calls a Cryptohome's method to perform offline login. +// Resultes are stored in a AuthAttemptState owned by CryptohomeAuthenticator +// and then call Resolve(). Resolve() will attempt to +// determine which AuthState we're in, based on the info at hand. +// It then triggers further action based on the calculated AuthState; this +// further action might include calling back the passed-in AuthStatusConsumer +// to signal that login succeeded or failed, waiting for more outstanding +// operations to complete, or triggering some more Cryptohome method calls. +// +// Typical flows +// ------------- +// Add new user: CONTINUE > CONTINUE > CREATE_NEW > CONTINUE > ONLINE_LOGIN +// Login as existing user: CONTINUE > OFFLINE_LOGIN +// Login as existing user (failure): CONTINUE > FAILED_MOUNT +// Change password detected: +// GAIA online ok: CONTINUE > CONTINUE > NEED_OLD_PW +// Recreate: CREATE_NEW > CONTINUE > ONLINE_LOGIN +// Old password failure: NEED_OLD_PW +// Old password ok: RECOVER_MOUNT > CONTINUE > ONLINE_LOGIN +// +class CHROMEOS_EXPORT CryptohomeAuthenticator + : public Authenticator, + public AuthAttemptStateResolver { + public: + enum AuthState { + CONTINUE = 0, // State indeterminate; try again with more info. + NO_MOUNT = 1, // Cryptohome doesn't exist yet. + FAILED_MOUNT = 2, // Failed to mount existing cryptohome. + FAILED_REMOVE = 3, // Failed to remove existing cryptohome. + FAILED_TMPFS = 4, // Failed to mount tmpfs for guest user. + FAILED_TPM = 5, // Failed to mount/create cryptohome, TPM error. + CREATE_NEW = 6, // Need to create cryptohome for a new user. + RECOVER_MOUNT = 7, // After RecoverEncryptedData, mount cryptohome. + POSSIBLE_PW_CHANGE = 8, // Offline login failed, user may have changed pw. + NEED_NEW_PW = 9, // Obsolete (ClientLogin): user changed pw, + // we have the old one. + NEED_OLD_PW = 10, // User changed pw, and we have the new one + // (GAIA auth is OK). + HAVE_NEW_PW = 11, // Obsolete (ClientLogin): We have verified new pw, + // time to migrate key. + OFFLINE_LOGIN = 12, // Login succeeded offline. + DEMO_LOGIN = 13, // Logged in as the demo user. + ONLINE_LOGIN = 14, // Offline and online login succeeded. + UNLOCK = 15, // Screen unlock succeeded. + ONLINE_FAILED = 16, // Obsolete (ClientLogin): Online login disallowed, + // but offline succeeded. + GUEST_LOGIN = 17, // Logged in guest mode. + PUBLIC_ACCOUNT_LOGIN = 18, // Logged into a public account. + SUPERVISED_USER_LOGIN = 19, // Logged in as a supervised user. + LOGIN_FAILED = 20, // Login denied. + OWNER_REQUIRED = 21, // Login is restricted to the owner only. + FAILED_USERNAME_HASH = 22, // Failed GetSanitizedUsername request. + KIOSK_ACCOUNT_LOGIN = 23, // Logged into a kiosk account. + REMOVED_DATA_AFTER_FAILURE = 24, // Successfully removed the user's + // cryptohome after a login failure. + }; + + CryptohomeAuthenticator(scoped_refptr<base::TaskRunner> task_runner, + AuthStatusConsumer* consumer); + + // Authenticator overrides. + virtual void CompleteLogin(Profile* profile, + const UserContext& user_context) OVERRIDE; + + // Given |user_context|, this method attempts to authenticate to your + // Chrome OS device. As soon as we have successfully mounted the encrypted + // home directory for the user, we will call consumer_->OnAuthSuccess() + // with the username. + // Upon failure to login consumer_->OnAuthFailure() is called + // with an error message. + // + // Uses |profile| when doing URL fetches. + virtual void AuthenticateToLogin(Profile* profile, + const UserContext& user_context) OVERRIDE; + + // Given |user_context|, this method attempts to authenticate to the cached + // user_context. This will never contact the server even if it's online. + // The auth result is sent to AuthStatusConsumer in a same way as + // AuthenticateToLogin does. + virtual void AuthenticateToUnlock(const UserContext& user_context) OVERRIDE; + + // Initiates supervised user login. + // Creates cryptohome if missing or mounts existing one and + // notifies consumer on the success/failure. + virtual void LoginAsSupervisedUser(const UserContext& user_context) OVERRIDE; + + // Initiates retail mode login. + // Mounts tmpfs and notifies consumer on the success/failure. + virtual void LoginRetailMode() OVERRIDE; + + // Initiates incognito ("browse without signing in") login. + // Mounts tmpfs and notifies consumer on the success/failure. + virtual void LoginOffTheRecord() OVERRIDE; + + // Initiates login into a public session. + // Mounts an ephemeral cryptohome and notifies consumer on the + // success/failure. + virtual void LoginAsPublicSession(const UserContext& user_context) OVERRIDE; + + // Initiates login into the kiosk mode account identified by |app_user_id|. + // Mounts an ephemeral guest cryptohome if |use_guest_mount| is |true|. + // Otherwise, mounts a public cryptohome, which will be ephemeral if the + // |DeviceEphemeralUsersEnabled| policy is enabled and non-ephemeral + // otherwise. + virtual void LoginAsKioskAccount(const std::string& app_user_id, + bool use_guest_mount) OVERRIDE; + + // These methods must be called on the UI thread, as they make DBus calls + // and also call back to the login UI. + virtual void OnRetailModeAuthSuccess() OVERRIDE; + virtual void OnAuthSuccess() OVERRIDE; + virtual void OnAuthFailure(const AuthFailure& error) OVERRIDE; + virtual void RecoverEncryptedData(const std::string& old_password) OVERRIDE; + virtual void ResyncEncryptedData() OVERRIDE; + + // AuthAttemptStateResolver overrides. + // Attempts to make a decision and call back |consumer_| based on + // the state we have gathered at the time of call. If a decision + // can't be made, defers until the next time this is called. + // When a decision is made, will call back to |consumer_| on the UI thread. + // + // Must be called on the UI thread. + virtual void Resolve() OVERRIDE; + + void OnOffTheRecordAuthSuccess(); + void OnPasswordChangeDetected(); + + protected: + virtual ~CryptohomeAuthenticator(); + + typedef base::Callback<void(bool is_owner)> IsOwnerCallback; + + // Method to be implemented in child. Return |true| if user specified in + // |context| exists on device. + virtual bool IsKnownUser(const UserContext& context) = 0; + + // Method to be implemented in child. Return |true| if device is running + // in safe mode. + virtual bool IsSafeMode() = 0; + + // Method to be implemented in child. Have to call |callback| with boolean + // parameter that indicates if user in |context| can act as an owner in + // safe mode. + virtual void CheckSafeModeOwnership(const UserContext& context, + const IsOwnerCallback& callback) = 0; + + private: + friend class CryptohomeAuthenticatorTest; + FRIEND_TEST_ALL_PREFIXES(CryptohomeAuthenticatorTest, + ResolveOwnerNeededDirectFailedMount); + FRIEND_TEST_ALL_PREFIXES(CryptohomeAuthenticatorTest, + ResolveOwnerNeededMount); + FRIEND_TEST_ALL_PREFIXES(CryptohomeAuthenticatorTest, + ResolveOwnerNeededFailedMount); + + // Removes the cryptohome of the user. + void RemoveEncryptedData(); + + // Returns the AuthState we're in, given the status info we have at + // the time of call. + // Must be called on the IO thread. + AuthState ResolveState(); + + // Helper for ResolveState(). + // Given that some cryptohome operation has failed, determine which of the + // possible failure states we're in. + // Must be called on the IO thread. + AuthState ResolveCryptohomeFailureState(); + + // Helper for ResolveState(). + // Given that some cryptohome operation has succeeded, determine which of + // the possible states we're in. + // Must be called on the IO thread. + AuthState ResolveCryptohomeSuccessState(); + + // Helper for ResolveState(). + // Given that some online auth operation has succeeded, determine which of + // the possible success states we're in. + // Must be called on the IO thread. + AuthState ResolveOnlineSuccessState(AuthState offline_state); + + // Used for testing. + void set_attempt_state(TestAttemptState* new_state) { // takes ownership. + current_state_.reset(new_state); + } + + // Used for testing to set the expected state of an owner check. + void SetOwnerState(bool owner_check_finished, bool check_result); + + // checks if the current mounted home contains the owner case and either + // continues or fails the log-in. Used for policy lost mitigation "safe-mode". + // Returns true if the owner check has been successful or if it is not needed. + bool VerifyOwner(); + + // Handles completion of the ownership check and continues login. + void OnOwnershipChecked(bool is_owner); + + // Signal login completion status for cases when a new user is added via + // an external authentication provider (i.e. GAIA extension). + void ResolveLoginCompletionStatus(); + + scoped_refptr<base::TaskRunner> task_runner_; + + scoped_ptr<AuthAttemptState> current_state_; + bool migrate_attempted_; + bool remove_attempted_; + bool resync_attempted_; + bool ephemeral_mount_attempted_; + bool check_key_attempted_; + + // When the user has changed her password, but gives us the old one, we will + // be able to mount her cryptohome, but online authentication will fail. + // This allows us to present the same behavior to the caller, regardless + // of the order in which we receive these results. + bool already_reported_success_; + base::Lock success_lock_; // A lock around |already_reported_success_|. + + // Flags signaling whether the owner verification has been done and the result + // of it. + bool owner_is_verified_; + bool user_can_login_; + + // Flag indicating to delete the user's cryptohome the login fails. + bool remove_user_data_on_failure_; + + // When |remove_user_data_on_failure_| is set, we delay calling + // consumer_->OnAuthFailure() until we removed the user cryptohome. + const AuthFailure* delayed_login_failure_; + + DISALLOW_COPY_AND_ASSIGN(CryptohomeAuthenticator); +}; + +} // namespace chromeos + +#endif // CHROMEOS_LOGIN_AUTH_CRYPTOHOME_AUTHENTICATOR_H_ |