blob: 8df7f61b8fc9282e4d42ed958f092cdddf35a1eb [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 "chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_signin_chromeos.h"
#include <stdint.h>
#include "base/base64url.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/json/json_string_value_serializer.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/system/sys_info.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_challenge_wrapper.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_key_manager.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_metrics.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager_factory.h"
#include "chrome/browser/chromeos/login/session/user_session_manager.h"
#include "chrome/common/pref_names.h"
#include "chromeos/chromeos_features.h"
#include "chromeos/components/multidevice/remote_device.h"
#include "chromeos/components/multidevice/remote_device_cache.h"
#include "chromeos/components/multidevice/remote_device_ref.h"
#include "chromeos/components/multidevice/software_feature_state.h"
#include "chromeos/components/proximity_auth/logging/logging.h"
#include "chromeos/components/proximity_auth/proximity_auth_local_state_pref_manager.h"
#include "chromeos/components/proximity_auth/smart_lock_metrics_recorder.h"
#include "chromeos/components/proximity_auth/switches.h"
#include "chromeos/login/auth/user_context.h"
#include "chromeos/login/login_state.h"
#include "chromeos/tpm/tpm_token_loader.h"
using proximity_auth::ScreenlockState;
namespace chromeos {
namespace {
// The maximum allowed backoff interval when waiting for cryptohome to start.
uint32_t kMaxCryptohomeBackoffIntervalMs = 10000u;
// If the data load fails, the initial interval after which the load will be
// retried. Further intervals will exponentially increas by factor 2.
uint32_t kInitialCryptohomeBackoffIntervalMs = 200u;
// Calculates the backoff interval that should be used next.
// |backoff| The last backoff interval used.
uint32_t GetNextBackoffInterval(uint32_t backoff) {
if (backoff == 0u)
return kInitialCryptohomeBackoffIntervalMs;
return backoff * 2;
}
void LoadDataForUser(
const AccountId& account_id,
uint32_t backoff_ms,
const EasyUnlockKeyManager::GetDeviceDataListCallback& callback);
// Callback passed to |LoadDataForUser()|.
// If |LoadDataForUser| function succeeded, it invokes |callback| with the
// results.
// If |LoadDataForUser| failed and further retries are allowed, schedules new
// |LoadDataForUser| call with some backoff. If no further retires are allowed,
// it invokes |callback| with the |LoadDataForUser| results.
void RetryDataLoadOnError(
const AccountId& account_id,
uint32_t backoff_ms,
const EasyUnlockKeyManager::GetDeviceDataListCallback& callback,
bool success,
const EasyUnlockDeviceKeyDataList& data_list) {
if (success) {
callback.Run(success, data_list);
return;
}
uint32_t next_backoff_ms = GetNextBackoffInterval(backoff_ms);
if (next_backoff_ms > kMaxCryptohomeBackoffIntervalMs) {
callback.Run(false, data_list);
return;
}
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&LoadDataForUser, account_id, next_backoff_ms, callback),
base::TimeDelta::FromMilliseconds(next_backoff_ms));
}
// Loads device data list associated with the user's Easy unlock keys.
void LoadDataForUser(
const AccountId& account_id,
uint32_t backoff_ms,
const EasyUnlockKeyManager::GetDeviceDataListCallback& callback) {
EasyUnlockKeyManager* key_manager =
UserSessionManager::GetInstance()->GetEasyUnlockKeyManager();
DCHECK(key_manager);
const user_manager::User* const user =
user_manager::UserManager::Get()->FindUser(account_id);
DCHECK(user);
key_manager->GetDeviceDataList(
UserContext(*user),
base::Bind(&RetryDataLoadOnError, account_id, backoff_ms, callback));
}
// Deserializes a vector of BeaconSeeds. If an error occurs, an empty vector
// will be returned. Note: The logic to serialize BeaconSeeds lives in
// EasyUnlockServiceRegular.
// Note: The serialization of device data inside a user session is different
// than outside the user session (sign-in). RemoteDevices are serialized as
// protocol buffers inside the user session, but we have a custom serialization
// scheme for sign-in due to slightly different data requirements.
std::vector<multidevice::BeaconSeed> DeserializeBeaconSeeds(
const std::string& serialized_beacon_seeds) {
std::vector<multidevice::BeaconSeed> beacon_seeds;
JSONStringValueDeserializer deserializer(serialized_beacon_seeds);
std::string error;
std::unique_ptr<base::Value> deserialized_value =
deserializer.Deserialize(nullptr, &error);
if (!deserialized_value) {
PA_LOG(ERROR) << "Unable to deserialize BeaconSeeds: " << error;
return beacon_seeds;
}
base::ListValue* beacon_seed_list;
if (!deserialized_value->GetAsList(&beacon_seed_list)) {
PA_LOG(ERROR) << "Deserialized BeaconSeeds value is not list.";
return beacon_seeds;
}
for (size_t i = 0; i < beacon_seed_list->GetSize(); ++i) {
std::string b64_beacon_seed;
if (!beacon_seed_list->GetString(i, &b64_beacon_seed)) {
PA_LOG(ERROR) << "Expected Base64 BeaconSeed.";
continue;
}
std::string proto_serialized_beacon_seed;
if (!base::Base64UrlDecode(b64_beacon_seed,
base::Base64UrlDecodePolicy::REQUIRE_PADDING,
&proto_serialized_beacon_seed)) {
PA_LOG(ERROR) << "Unable to decode BeaconSeed.";
continue;
}
cryptauth::BeaconSeed beacon_seed;
if (!beacon_seed.ParseFromString(proto_serialized_beacon_seed)) {
PA_LOG(ERROR) << "Unable to parse BeaconSeed proto.";
continue;
}
beacon_seeds.push_back(
chromeos::multidevice::FromCryptAuthSeed(beacon_seed));
}
PA_LOG(VERBOSE) << "Deserialized " << beacon_seeds.size() << " BeaconSeeds.";
return beacon_seeds;
}
} // namespace
EasyUnlockServiceSignin::UserData::UserData()
: state(EasyUnlockServiceSignin::USER_DATA_STATE_INITIAL) {}
EasyUnlockServiceSignin::UserData::~UserData() {}
EasyUnlockServiceSignin::EasyUnlockServiceSignin(
Profile* profile,
secure_channel::SecureChannelClient* secure_channel_client)
: EasyUnlockService(profile, secure_channel_client),
account_id_(EmptyAccountId()),
user_pod_last_focused_timestamp_(base::TimeTicks::Now()),
remote_device_cache_(
multidevice::RemoteDeviceCache::Factory::Get()->BuildInstance()),
weak_ptr_factory_(this) {}
EasyUnlockServiceSignin::~EasyUnlockServiceSignin() {}
void EasyUnlockServiceSignin::WrapChallengeForUserAndDevice(
const AccountId& account_id,
const std::string& device_public_key,
const std::string& channel_binding_data,
base::Callback<void(const std::string& wraped_challenge)> callback) {
auto it = user_data_.find(account_id);
if (it == user_data_.end() || it->second->state != USER_DATA_STATE_LOADED) {
PA_LOG(ERROR) << "TPM data not loaded for " << account_id.Serialize();
callback.Run(std::string());
return;
}
std::string device_public_key_base64;
base::Base64UrlEncode(device_public_key,
base::Base64UrlEncodePolicy::INCLUDE_PADDING,
&device_public_key_base64);
for (const auto& device_data : it->second->devices) {
if (device_data.public_key == device_public_key_base64) {
PA_LOG(VERBOSE) << "Wrapping challenge for " << account_id.Serialize()
<< "...";
challenge_wrapper_.reset(new EasyUnlockChallengeWrapper(
device_data.challenge, channel_binding_data, account_id,
EasyUnlockTpmKeyManagerFactory::GetInstance()->Get(profile())));
challenge_wrapper_->WrapChallenge(callback);
return;
}
}
PA_LOG(ERROR) << "Unable to find device record for "
<< account_id.Serialize();
callback.Run(std::string());
}
proximity_auth::ProximityAuthPrefManager*
EasyUnlockServiceSignin::GetProximityAuthPrefManager() {
return pref_manager_.get();
}
EasyUnlockService::Type EasyUnlockServiceSignin::GetType() const {
return EasyUnlockService::TYPE_SIGNIN;
}
AccountId EasyUnlockServiceSignin::GetAccountId() const {
return account_id_;
}
void EasyUnlockServiceSignin::ClearPermitAccess() {
NOTREACHED();
}
const base::ListValue* EasyUnlockServiceSignin::GetRemoteDevices() const {
const UserData* data = FindLoadedDataForCurrentUser();
if (!data)
return nullptr;
return &data->remote_devices_value;
}
void EasyUnlockServiceSignin::SetRemoteDevices(const base::ListValue& devices) {
NOTREACHED();
}
std::string EasyUnlockServiceSignin::GetChallenge() const {
const UserData* data = FindLoadedDataForCurrentUser();
if (!data)
return std::string();
for (const auto& device : data->devices) {
if (device.unlock_key)
return device.challenge;
}
return std::string();
}
std::string EasyUnlockServiceSignin::GetWrappedSecret() const {
const UserData* data = FindLoadedDataForCurrentUser();
if (!data)
return std::string();
for (const auto& device : data->devices) {
if (device.unlock_key)
return device.wrapped_secret;
}
return std::string();
}
void EasyUnlockServiceSignin::RecordEasySignInOutcome(
const AccountId& account_id,
bool success) const {
DCHECK(GetAccountId() == account_id)
<< "GetAccountId()=" << GetAccountId().Serialize()
<< " != account_id=" << account_id.Serialize();
RecordEasyUnlockSigninEvent(success ? EASY_UNLOCK_SUCCESS
: EASY_UNLOCK_FAILURE);
if (success) {
RecordEasyUnlockSigninDuration(base::TimeTicks::Now() -
user_pod_last_focused_timestamp_);
}
DVLOG(1) << "Easy sign-in " << (success ? "success" : "failure");
}
void EasyUnlockServiceSignin::RecordPasswordLoginEvent(
const AccountId& account_id) const {
// This happens during tests, where a user could log in without the user pod
// being focused.
if (GetAccountId() != account_id)
return;
if (!IsEnabled())
return;
EasyUnlockAuthEvent event = GetPasswordAuthEvent();
RecordEasyUnlockSigninEvent(event);
DVLOG(1) << "Easy Sign-in password login event, event=" << event;
}
void EasyUnlockServiceSignin::InitializeInternal() {
if (LoginState::Get()->IsUserLoggedIn())
return;
service_active_ = true;
pref_manager_.reset(new proximity_auth::ProximityAuthLocalStatePrefManager(
g_browser_process->local_state()));
proximity_auth::ScreenlockBridge* screenlock_bridge =
proximity_auth::ScreenlockBridge::Get();
screenlock_bridge->AddObserver(this);
if (screenlock_bridge->focused_account_id().is_valid())
OnFocusedUserChanged(screenlock_bridge->focused_account_id());
}
void EasyUnlockServiceSignin::ShutdownInternal() {
if (!service_active_)
return;
service_active_ = false;
weak_ptr_factory_.InvalidateWeakPtrs();
proximity_auth::ScreenlockBridge::Get()->RemoveObserver(this);
user_data_.clear();
}
bool EasyUnlockServiceSignin::IsAllowedInternal() const {
return service_active_ && account_id_.is_valid() &&
!LoginState::Get()->IsUserLoggedIn() &&
(pref_manager_ && pref_manager_->IsEasyUnlockAllowed() &&
pref_manager_->IsChromeOSLoginAllowed());
}
bool EasyUnlockServiceSignin::IsEnabled() const {
return pref_manager_->IsEasyUnlockEnabled();
}
bool EasyUnlockServiceSignin::IsChromeOSLoginEnabled() const {
return pref_manager_ && pref_manager_->IsChromeOSLoginEnabled();
}
void EasyUnlockServiceSignin::OnWillFinalizeUnlock(bool success) {
// This code path should only be exercised for the lock screen, not for the
// sign-in screen.
NOTREACHED();
}
void EasyUnlockServiceSignin::OnSuspendDoneInternal() {
// Ignored.
}
void EasyUnlockServiceSignin::OnBluetoothAdapterPresentChanged() {
// Because the BluetoothAdapter state change may change whether EasyUnlock is
// allowed, we want to treat the user pod as though it were focused for the
// first time. This allows the correct flow (loading cryptohome keys,
// initializing ProximityAuthSystem, etc.) to take place.
AccountId current_account_id = account_id_;
account_id_ = AccountId();
OnFocusedUserChanged(current_account_id);
}
void EasyUnlockServiceSignin::OnScreenDidLock(
proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type) {
// In production code, the screen type should always be the signin screen; but
// in tests, the screen type might be different.
if (screen_type !=
proximity_auth::ScreenlockBridge::LockHandler::SIGNIN_SCREEN)
return;
// Update initial UI is when the account picker on login screen is ready.
ShowInitialUserPodState();
user_pod_last_focused_timestamp_ = base::TimeTicks::Now();
}
void EasyUnlockServiceSignin::OnScreenDidUnlock(
proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type) {
// In production code, the screen type should always be the signin screen; but
// in tests, the screen type might be different.
if (screen_type !=
proximity_auth::ScreenlockBridge::LockHandler::SIGNIN_SCREEN)
return;
// Only record metrics for users who have enabled the feature.
if (IsEnabled()) {
EasyUnlockAuthEvent event = GetPasswordAuthEvent();
if (event == PASSWORD_ENTRY_PHONE_LOCKED ||
event == PASSWORD_ENTRY_PHONE_NOT_LOCKABLE ||
event == PASSWORD_ENTRY_RSSI_TOO_LOW ||
event == PASSWORD_ENTRY_PHONE_LOCKED_AND_RSSI_TOO_LOW ||
event == PASSWORD_ENTRY_WITH_AUTHENTICATED_PHONE) {
SmartLockMetricsRecorder::RecordGetRemoteStatusResultSignInSuccess();
} else if (event == PASSWORD_ENTRY_BLUETOOTH_CONNECTING) {
SmartLockMetricsRecorder::RecordGetRemoteStatusResultSignInFailure(
SmartLockMetricsRecorder::
SmartLockGetRemoteStatusResultFailureReason::
kUserEnteredPasswordWhileConnecting);
} else if (event == PASSWORD_ENTRY_NO_BLUETOOTH) {
SmartLockMetricsRecorder::RecordGetRemoteStatusResultSignInFailure(
SmartLockMetricsRecorder::
SmartLockGetRemoteStatusResultFailureReason::
kUserEnteredPasswordWhileBluetoothDisabled);
}
}
Shutdown();
}
void EasyUnlockServiceSignin::OnFocusedUserChanged(
const AccountId& account_id) {
if (account_id_ == account_id)
return;
// Setting or clearing the account_id may changed |IsAllowed| value, so in
// these cases update the app state. Otherwise, it's enough to notify the app
// the user data has been updated.
const bool should_update_app_state = (account_id_ != account_id);
account_id_ = account_id;
pref_manager_->SetActiveUser(account_id);
user_pod_last_focused_timestamp_ = base::TimeTicks::Now();
SetProximityAuthDevices(account_id_, multidevice::RemoteDeviceRefList(),
base::nullopt /* local_device */);
ResetScreenlockState();
pref_manager_->SetActiveUser(account_id);
if (!IsAllowed() || !IsEnabled())
return;
ShowInitialUserPodState();
// If there is a hardlock, then there is no point in loading the devices.
EasyUnlockScreenlockStateHandler::HardlockState hardlock_state;
if (GetPersistedHardlockState(&hardlock_state) &&
hardlock_state != EasyUnlockScreenlockStateHandler::NO_HARDLOCK) {
PA_LOG(VERBOSE) << "Hardlock present, skipping remaining login flow.";
return;
}
if (should_update_app_state) {
UpdateAppState();
}
LoadCurrentUserDataIfNeeded();
// Start loading TPM system token.
// The system token will be needed to sign a nonce using TPM private key
// during the sign-in protocol.
TPMTokenLoader::Get()->EnsureStarted();
}
void EasyUnlockServiceSignin::LoadCurrentUserDataIfNeeded() {
// TODO(xiyuan): Revisit this when adding tests.
if (!base::SysInfo::IsRunningOnChromeOS())
return;
if (!account_id_.is_valid() || !service_active_)
return;
const auto it = user_data_.find(account_id_);
if (it == user_data_.end())
user_data_.insert(
std::make_pair(account_id_, std::make_unique<UserData>()));
UserData* data = user_data_[account_id_].get();
if (data->state == USER_DATA_STATE_LOADING)
return;
data->state = USER_DATA_STATE_LOADING;
LoadDataForUser(
account_id_,
allow_cryptohome_backoff_ ? 0u : kMaxCryptohomeBackoffIntervalMs,
base::Bind(&EasyUnlockServiceSignin::OnUserDataLoaded,
weak_ptr_factory_.GetWeakPtr(), account_id_));
}
// TODO(crbug.com/856387): Write tests for device retrieval from the TPM.
void EasyUnlockServiceSignin::OnUserDataLoaded(
const AccountId& account_id,
bool success,
const EasyUnlockDeviceKeyDataList& devices) {
allow_cryptohome_backoff_ = false;
UserData* data = user_data_[account_id].get();
data->state = USER_DATA_STATE_LOADED;
if (success) {
data->devices = devices;
EasyUnlockKeyManager::DeviceDataListToRemoteDeviceList(
account_id, devices, &data->remote_devices_value);
// User could have a NO_HARDLOCK state but has no remote devices if
// previous user session shuts down before
// CheckCryptohomeKeysAndMaybeHardlock finishes. Set NO_PAIRING state
// and update UI to remove the confusing spinner in this case.
EasyUnlockScreenlockStateHandler::HardlockState hardlock_state;
if (devices.empty() && GetPersistedHardlockState(&hardlock_state) &&
hardlock_state == EasyUnlockScreenlockStateHandler::NO_HARDLOCK) {
SetHardlockStateForUser(account_id,
EasyUnlockScreenlockStateHandler::NO_PAIRING);
}
}
if (devices.empty())
return;
multidevice::RemoteDeviceList remote_devices;
for (const auto& device : devices) {
std::string decoded_public_key, decoded_psk;
if (!base::Base64UrlDecode(device.public_key,
base::Base64UrlDecodePolicy::REQUIRE_PADDING,
&decoded_public_key) ||
!base::Base64UrlDecode(device.psk,
base::Base64UrlDecodePolicy::REQUIRE_PADDING,
&decoded_psk)) {
PA_LOG(ERROR) << "Unable to decode stored remote device:\n"
<< " public_key: " << device.public_key << "\n"
<< " psk: " << device.psk;
continue;
}
std::map<multidevice::SoftwareFeature, multidevice::SoftwareFeatureState>
software_features;
software_features[multidevice::SoftwareFeature::kSmartLockHost] =
device.unlock_key ? multidevice::SoftwareFeatureState::kEnabled
: multidevice::SoftwareFeatureState::kNotSupported;
std::vector<multidevice::BeaconSeed> beacon_seeds;
if (!device.serialized_beacon_seeds.empty()) {
PA_LOG(VERBOSE) << "Deserializing BeaconSeeds: "
<< device.serialized_beacon_seeds;
beacon_seeds = DeserializeBeaconSeeds(device.serialized_beacon_seeds);
} else {
PA_LOG(WARNING) << "No BeaconSeeds were loaded.";
}
multidevice::RemoteDevice remote_device(
account_id.GetUserEmail(), std::string() /* name */, decoded_public_key,
decoded_psk /* persistent_symmetric_key */,
0L /* last_update_time_millis */, software_features, beacon_seeds);
remote_devices.push_back(remote_device);
PA_LOG(VERBOSE) << "Loaded Remote Device:\n"
<< " user id: " << remote_device.user_id << "\n"
<< " device id: "
<< multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
remote_device.GetDeviceId());
}
// If |chromeos::features::kMultiDeviceApi| is enabled, both a remote device
// and local device are expected, and this service cannot continue unless
// both are present.
//
// If the flag is disabled, just one device, the remote device, is expected to
// be passed along -- if a second device is present, it can simply be ignored.
//
// TODO(crbug.com/856380): The remote and local devices need to be passed in a
// less hacky way.
if (remote_devices.size() > 2u) {
PA_LOG(ERROR)
<< "Expected a device list of size 1 or 2, received list of size "
<< remote_devices.size();
SetHardlockStateForUser(account_id,
EasyUnlockScreenlockStateHandler::NO_PAIRING);
return;
}
if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi) &&
remote_devices.size() != 2u) {
PA_LOG(ERROR) << "Expected a device list of size 2, received list of size "
<< remote_devices.size();
SetHardlockStateForUser(account_id,
EasyUnlockScreenlockStateHandler::PAIRING_CHANGED);
return;
}
std::string unlock_key_id;
// This may be left unset if the local device was not passed along.
std::string local_device_id;
for (const auto& remote_device : remote_devices) {
if (base::ContainsKey(remote_device.software_features,
multidevice::SoftwareFeature::kSmartLockHost) &&
remote_device.software_features.at(
multidevice::SoftwareFeature::kSmartLockHost) ==
multidevice::SoftwareFeatureState::kEnabled) {
if (!unlock_key_id.empty()) {
PA_LOG(ERROR) << "Only one of the devices should be an unlock key.";
SetHardlockStateForUser(account_id,
EasyUnlockScreenlockStateHandler::NO_PAIRING);
return;
}
unlock_key_id = remote_device.GetDeviceId();
} else {
if (!local_device_id.empty()) {
PA_LOG(ERROR) << "Only one of the devices should be the local device.";
SetHardlockStateForUser(account_id,
EasyUnlockScreenlockStateHandler::NO_PAIRING);
return;
}
local_device_id = remote_device.GetDeviceId();
}
}
remote_device_cache_->SetRemoteDevices(remote_devices);
base::Optional<multidevice::RemoteDeviceRef> unlock_key_device =
remote_device_cache_->GetRemoteDevice(unlock_key_id);
base::Optional<multidevice::RemoteDeviceRef> local_device =
remote_device_cache_->GetRemoteDevice(local_device_id);
// TODO(hansberry): It is possible that there may not be an unlock key by this
// point. If this occurs, it is due to a bug in how device metadata is
// persisted in CryptoHome. See https://crbug.com/856380 for more details. For
// now, simply return early here to prevent a potential crash which can occur
// in this situation (see https://crbug.com/866711).
if (!unlock_key_device) {
SetHardlockStateForUser(account_id,
EasyUnlockScreenlockStateHandler::NO_PAIRING);
return;
}
// Likewise, a similar issue could exist when the kMultiDeviceApi flag is
// enabled.
if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi) &&
!local_device) {
SetHardlockStateForUser(account_id,
EasyUnlockScreenlockStateHandler::NO_PAIRING);
return;
}
SetProximityAuthDevices(account_id, {*unlock_key_device}, local_device);
}
const EasyUnlockServiceSignin::UserData*
EasyUnlockServiceSignin::FindLoadedDataForCurrentUser() const {
if (!account_id_.is_valid())
return nullptr;
const auto it = user_data_.find(account_id_);
if (it == user_data_.end())
return nullptr;
if (it->second->state != USER_DATA_STATE_LOADED)
return nullptr;
return it->second.get();
}
void EasyUnlockServiceSignin::ShowInitialUserPodState() {
if (!IsAllowed() || !IsEnabled())
return;
if (!pref_manager_->IsChromeOSLoginEnabled()) {
// Show a hardlock state if the user has not enabled the login flow.
SetHardlockStateForUser(
account_id_,
EasyUnlockScreenlockStateHandler::PASSWORD_REQUIRED_FOR_LOGIN);
} else {
// This UI is simply a placeholder until the RemoteDevices are loaded from
// cryptohome and the ProximityAuthSystem is started. Hardlock states are
// automatically taken into account.
UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING);
}
}
} // namespace chromeos