Commit a83f3108 authored by Jacob Dufault's avatar Jacob Dufault Committed by Commit Bot

cros: Enable PIN sign-in.

If the cryptohome supports low-entropy credentials, registering a PIN
will use the cryptohome backend.

We prehash the PIN before submitting to cryptohome so we can automatically
migrate existing prefs-based PINs when the user signs in. This will be
done in a follow-up CL.

Bug: 826773
Change-Id: I7e876a0a3bb4ab9681df08873cb4cd1465489e58
Reviewed-on: https://chromium-review.googlesource.com/1033212
Commit-Queue: Jacob Dufault <jdufault@chromium.org>
Reviewed-by: default avatarAchuith Bhandarkar <achuith@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#560754}
parent ef82a4ef
......@@ -32,6 +32,7 @@
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_service.h"
#include "chrome/browser/chromeos/login/enterprise_user_session_metrics.h"
#include "chrome/browser/chromeos/login/helper.h"
#include "chrome/browser/chromeos/login/quick_unlock/pin_storage_cryptohome.h"
#include "chrome/browser/chromeos/login/reauth_stats.h"
#include "chrome/browser/chromeos/login/screens/encryption_migration_screen.h"
#include "chrome/browser/chromeos/login/session/user_session_manager.h"
......@@ -625,6 +626,16 @@ void ExistingUserController::PerformLogin(
auth_mode == LoginPerformer::AUTH_MODE_EXTENSION));
}
if (new_user_context.IsUsingPin()) {
base::Optional<Key> key = quick_unlock::PinStorageCryptohome::TransformKey(
new_user_context.GetAccountId(), *new_user_context.GetKey());
if (key) {
new_user_context.SetKey(*key);
} else {
new_user_context.SetIsUsingPin(false);
}
}
if (user_manager::UserManager::Get()->IsSupervisedAccountId(
user_context.GetAccountId())) {
login_performer_->LoginAsSupervisedUser(new_user_context);
......
......@@ -378,12 +378,9 @@ void ScreenLocker::Authenticate(const UserContext& user_context,
const user_manager::User* user = FindUnlockUser(user_context.GetAccountId());
if (user) {
// Check to see if the user submitted a PIN and it is valid.
const std::string pin = user_context.GetKey()->GetSecret();
Key::KeyType key_type = user_context.GetKey()->GetKeyType();
if (unlock_attempt_type_ == AUTH_PIN) {
quick_unlock::PinBackend::GetInstance()->TryAuthenticate(
user_context.GetAccountId(), pin, key_type,
user_context.GetAccountId(), *user_context.GetKey(),
base::BindOnce(&ScreenLocker::OnPinAttemptDone,
weak_factory_.GetWeakPtr(), user_context));
// OnPinAttemptDone will call ContinueAuthenticate.
......
......@@ -18,6 +18,7 @@
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/lock_screen_apps/state_controller.h"
#include "chrome/browser/chromeos/login/lock_screen_utils.h"
#include "chrome/browser/chromeos/login/quick_unlock/pin_backend.h"
#include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_factory.h"
#include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_storage.h"
#include "chrome/browser/chromeos/login/screens/chrome_user_selection_screen.h"
......@@ -318,14 +319,9 @@ void ViewsScreenLocker::OnDeviceInfoUpdated(const std::string& bluetooth_name) {
}
void ViewsScreenLocker::UpdatePinKeyboardState(const AccountId& account_id) {
quick_unlock::QuickUnlockStorage* quick_unlock_storage =
quick_unlock::QuickUnlockFactory::GetForAccountId(account_id);
if (!quick_unlock_storage)
return;
bool is_enabled = quick_unlock_storage->IsPinAuthenticationAvailable();
LoginScreenClient::Get()->login_screen()->SetPinEnabledForUser(account_id,
is_enabled);
quick_unlock::PinBackend::GetInstance()->CanAuthenticate(
account_id, base::BindOnce(&ViewsScreenLocker::OnPinCanAuthenticate,
weak_factory_.GetWeakPtr(), account_id));
}
void ViewsScreenLocker::OnAllowedInputMethodsChanged() {
......@@ -346,4 +342,10 @@ void ViewsScreenLocker::OnDevChannelInfoUpdated() {
os_version_label_text_, enterprise_info_text_, bluetooth_name_);
}
void ViewsScreenLocker::OnPinCanAuthenticate(const AccountId& account_id,
bool can_authenticate) {
LoginScreenClient::Get()->login_screen()->SetPinEnabledForUser(
account_id, can_authenticate);
}
} // namespace chromeos
......@@ -84,6 +84,7 @@ class ViewsScreenLocker : public LoginScreenClient::Delegate,
void UpdatePinKeyboardState(const AccountId& account_id);
void OnAllowedInputMethodsChanged();
void OnDevChannelInfoUpdated();
void OnPinCanAuthenticate(const AccountId& account_id, bool can_authenticate);
std::unique_ptr<UserBoardViewMojo> user_board_view_mojo_;
std::unique_ptr<UserSelectionScreen> user_selection_screen_;
......
......@@ -4,7 +4,8 @@
#include "chrome/browser/chromeos/login/quick_unlock/pin_backend.h"
#include "base/no_destructor.h"
#include "base/base64.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/chromeos/login/quick_unlock/pin_storage_cryptohome.h"
#include "chrome/browser/chromeos/login/quick_unlock/pin_storage_prefs.h"
......@@ -12,12 +13,15 @@
#include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_storage.h"
#include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h"
#include "components/account_id/account_id.h"
#include "crypto/random.h"
namespace chromeos {
namespace quick_unlock {
namespace {
constexpr int kSaltByteSize = 16;
QuickUnlockStorage* GetPrefsBackend(const AccountId& account_id) {
return QuickUnlockFactory::GetForAccountId(account_id);
}
......@@ -27,23 +31,56 @@ void PostResponse(PinBackend::BoolCallback result, bool value) {
FROM_HERE, base::BindOnce(std::move(result), value));
}
PinBackend* g_instance_ = nullptr;
} // namespace
// static
PinBackend* PinBackend::GetInstance() {
static base::NoDestructor<PinBackend> instance;
return instance.get();
if (!g_instance_) {
g_instance_ = new PinBackend();
}
return g_instance_;
}
// static
std::string PinBackend::ComputeSalt() {
// The salt needs to be base64 encoded because the pref service requires a
// UTF8 string.
std::string salt;
crypto::RandBytes(base::WriteInto(&salt, kSaltByteSize + 1), kSaltByteSize);
base::Base64Encode(salt, &salt);
DCHECK(!salt.empty());
return salt;
}
// static
std::string PinBackend::ComputeSecret(const std::string& pin,
const std::string& salt,
Key::KeyType key_type) {
DCHECK(key_type == Key::KEY_TYPE_PASSWORD_PLAIN ||
key_type == Key::KEY_TYPE_SALTED_PBKDF2_AES256_1234);
DCHECK(!pin.empty());
DCHECK(!salt.empty());
if (key_type != Key::KEY_TYPE_PASSWORD_PLAIN)
return pin;
Key key(pin);
key.Transform(chromeos::Key::KEY_TYPE_SALTED_PBKDF2_AES256_1234, salt);
return key.GetSecret();
}
// static
void PinBackend::ResetForTesting() {
new (GetInstance()) PinStorageCryptohome();
delete g_instance_;
g_instance_ = nullptr;
}
PinBackend::PinBackend() {
// Always use prefs backend.
// TODO(jdufault): Add support for cryptohome backend.
resolving_backend_ = false;
// base::Unretained is safe because the PinBackend instance is never
// destroyed.
PinStorageCryptohome::IsSupported(base::BindOnce(
&PinBackend::OnIsCryptohomeBackendSupported, base::Unretained(this)));
}
PinBackend::~PinBackend() {
......@@ -58,7 +95,7 @@ void PinBackend::IsSet(const AccountId& account_id, BoolCallback result) {
return;
}
if (cryptohome_backend_) {
if (ShouldUseCryptohome(account_id)) {
cryptohome_backend_->IsPinSetInCryptohome(account_id, std::move(result));
} else {
QuickUnlockStorage* storage = GetPrefsBackend(account_id);
......@@ -136,8 +173,8 @@ void PinBackend::CanAuthenticate(const AccountId& account_id,
return;
}
if (cryptohome_backend_) {
cryptohome_backend_->IsPinSetInCryptohome(account_id, std::move(result));
if (ShouldUseCryptohome(account_id)) {
cryptohome_backend_->CanAuthenticate(account_id, std::move(result));
} else {
QuickUnlockStorage* storage = GetPrefsBackend(account_id);
PostResponse(
......@@ -148,19 +185,17 @@ void PinBackend::CanAuthenticate(const AccountId& account_id,
}
void PinBackend::TryAuthenticate(const AccountId& account_id,
const std::string& key,
const Key::KeyType& key_type,
const Key& key,
BoolCallback result) {
if (resolving_backend_) {
on_cryptohome_support_received_.push_back(
base::BindOnce(&PinBackend::TryAuthenticate, base::Unretained(this),
account_id, key, key_type, std::move(result)));
account_id, key, std::move(result)));
return;
}
if (cryptohome_backend_) {
cryptohome_backend_->TryAuthenticate(account_id, key, key_type,
std::move(result));
if (ShouldUseCryptohome(account_id)) {
cryptohome_backend_->TryAuthenticate(account_id, key, std::move(result));
} else {
QuickUnlockStorage* storage = GetPrefsBackend(account_id);
DCHECK(storage);
......@@ -168,24 +203,30 @@ void PinBackend::TryAuthenticate(const AccountId& account_id,
if (!storage->HasStrongAuth()) {
PostResponse(std::move(result), false);
} else {
PostResponse(
std::move(result),
storage->pin_storage_prefs()->TryAuthenticatePin(key, key_type));
PostResponse(std::move(result),
storage->pin_storage_prefs()->TryAuthenticatePin(key));
}
}
}
std::string PinBackend::ComputeSecret(const std::string& pin,
const std::string& salt,
Key::KeyType key_type) {
DCHECK(!pin.empty());
DCHECK(!salt.empty());
if (key_type != Key::KEY_TYPE_PASSWORD_PLAIN)
return pin;
void PinBackend::OnIsCryptohomeBackendSupported(bool is_supported) {
if (is_supported)
cryptohome_backend_ = std::make_unique<PinStorageCryptohome>();
resolving_backend_ = false;
for (auto& callback : on_cryptohome_support_received_)
std::move(callback).Run();
on_cryptohome_support_received_.clear();
}
Key key(pin);
key.Transform(Key::KEY_TYPE_SALTED_SHA256_TOP_HALF, salt);
return key.GetSecret();
bool PinBackend::ShouldUseCryptohome(const AccountId& account_id) {
if (!cryptohome_backend_)
return false;
// Even if cryptohome is supported, the user may have registered a PIN with
// the prefs backend from a previous version. If that's the case, we should
// talk to the prefs backend instead of the cryptohome backend.
QuickUnlockStorage* storage = GetPrefsBackend(account_id);
return !storage->pin_storage_prefs()->IsPinSet();
}
} // namespace quick_unlock
......
......@@ -27,6 +27,14 @@ class PinBackend {
// Fetch the PinBackend instance.
static PinBackend* GetInstance();
// Computes a new salt.
static std::string ComputeSalt();
// Computes the secret for a given |pin| and |salt|.
static std::string ComputeSecret(const std::string& pin,
const std::string& salt,
Key::KeyType key_type);
// Use GetInstance().
PinBackend();
~PinBackend();
......@@ -52,19 +60,21 @@ class PinBackend {
// Try to authenticate.
void TryAuthenticate(const AccountId& account_id,
const std::string& key,
const Key::KeyType& key_type,
const Key& key,
BoolCallback result);
// Computes the secret for a given |pin| and |salt|.
std::string ComputeSecret(const std::string& pin,
const std::string& salt,
Key::KeyType key_type);
// Resets any cached state for testing purposes.
static void ResetForTesting();
private:
// Called when we know if the cryptohome supports PIN.
void OnIsCryptohomeBackendSupported(bool is_supported);
// Returns true if the cryptohome backend should be used. Sometimes the prefs
// backend should be used even when cryptohome is available, ie, when there is
// an non-migrated PIN key.
bool ShouldUseCryptohome(const AccountId& account_id);
// True if still trying to determine which backend should be used.
bool resolving_backend_ = true;
// Determining if the device supports cryptohome-based keys requires an async
......
......@@ -8,6 +8,8 @@
#include <string>
#include "base/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "chromeos/login/auth/user_context.h"
class AccountId;
......@@ -22,6 +24,14 @@ class PinStorageCryptohome {
public:
using BoolCallback = base::OnceCallback<void(bool)>;
// Check to see if the cryptohome implementation can store PINs.
static void IsSupported(BoolCallback result);
// Transforms |key| for usage in PIN. Returns nullopt if the key could not be
// transformed.
static base::Optional<Key> TransformKey(const AccountId& account_id,
const Key& key);
PinStorageCryptohome();
~PinStorageCryptohome();
......@@ -31,12 +41,20 @@ class PinStorageCryptohome {
const std::string& pin,
BoolCallback did_set);
void RemovePin(const UserContext& user_context, BoolCallback did_remove);
void CanAuthenticate(const AccountId& account_id, BoolCallback result) const;
void TryAuthenticate(const AccountId& account_id,
const std::string& key,
const Key::KeyType& key_type,
const Key& key,
BoolCallback result);
private:
void OnSystemSaltObtained(const std::string& system_salt);
bool salt_obtained_ = false;
std::string system_salt_;
std::vector<base::OnceClosure> system_salt_callbacks_;
base::WeakPtrFactory<PinStorageCryptohome> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(PinStorageCryptohome);
};
......
......@@ -5,46 +5,15 @@
#include "chrome/browser/chromeos/login/quick_unlock/pin_storage_prefs.h"
#include "ash/public/cpp/ash_pref_names.h"
#include "base/base64.h"
#include "base/strings/string_util.h"
#include "chrome/browser/chromeos/login/quick_unlock/pin_backend.h"
#include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "crypto/random.h"
namespace chromeos {
namespace quick_unlock {
namespace {
const int kSaltByteSize = 16;
// Returns a new salt of length |kSaltByteSize|.
std::string CreateSalt() {
// The salt needs to be base64 encoded because the pref service requires a
// UTF8 string.
std::string salt;
crypto::RandBytes(base::WriteInto(&salt, kSaltByteSize + 1), kSaltByteSize);
base::Base64Encode(salt, &salt);
DCHECK(!salt.empty());
return salt;
}
// Computes the hash for |pin| using |salt| and |key_type|.
std::string ComputeSecret(const std::string& pin,
const std::string& salt,
Key::KeyType key_type) {
if (key_type != Key::KEY_TYPE_PASSWORD_PLAIN)
return pin;
Key key(pin);
key.Transform(Key::KEY_TYPE_SALTED_PBKDF2_AES256_1234, salt);
return key.GetSecret();
}
} // namespace
// static
void PinStoragePrefs::RegisterProfilePrefs(PrefRegistrySimple* registry) {
// Mark it as PUBLIC so ash could access this pref.
......@@ -71,9 +40,9 @@ bool PinStoragePrefs::IsPinSet() const {
}
void PinStoragePrefs::SetPin(const std::string& pin) {
const std::string salt = CreateSalt();
const std::string salt = PinBackend::ComputeSalt();
const std::string secret =
ComputeSecret(pin, salt, Key::KEY_TYPE_PASSWORD_PLAIN);
PinBackend::ComputeSecret(pin, salt, Key::KEY_TYPE_PASSWORD_PLAIN);
pref_service_->SetString(ash::prefs::kQuickUnlockPinSalt, salt);
pref_service_->SetString(prefs::kQuickUnlockPinSecret, secret);
......@@ -100,13 +69,13 @@ bool PinStoragePrefs::IsPinAuthenticationAvailable() const {
IsPinSet() && !exceeded_unlock_attempts;
}
bool PinStoragePrefs::TryAuthenticatePin(const std::string& pin,
Key::KeyType key_type) {
bool PinStoragePrefs::TryAuthenticatePin(const Key& key) {
if (!IsPinAuthenticationAvailable())
return false;
AddUnlockAttempt();
return ComputeSecret(pin, PinSalt(), key_type) == PinSecret();
return PinBackend::ComputeSecret(key.GetSecret(), PinSalt(),
key.GetKeyType()) == PinSecret();
}
} // namespace quick_unlock
......
......@@ -54,7 +54,7 @@ class PinStoragePrefs {
// Tries to authenticate the given pin. This will consume an unlock attempt.
// This always returns false if IsPinAuthenticationAvailable returns false.
bool TryAuthenticatePin(const std::string& pin, Key::KeyType key_type);
bool TryAuthenticatePin(const Key& key);
private:
friend class chromeos::PinStoragePrefsTestApi;
......
......@@ -51,8 +51,8 @@ class PinStoragePrefsTestApi {
bool IsPinAuthenticationAvailable() const {
return pin_storage_->IsPinAuthenticationAvailable();
}
bool TryAuthenticatePin(const std::string& pin, Key::KeyType key_type) {
return pin_storage_->TryAuthenticatePin(pin, key_type);
bool TryAuthenticatePin(const std::string& secret, Key::KeyType key_type) {
return pin_storage_->TryAuthenticatePin(Key(key_type, "" /*salt*/, secret));
}
private:
......
......@@ -56,10 +56,8 @@ bool QuickUnlockStorage::IsPinAuthenticationAvailable() const {
return HasStrongAuth() && pin_storage_prefs_->IsPinAuthenticationAvailable();
}
bool QuickUnlockStorage::TryAuthenticatePin(const std::string& pin,
Key::KeyType key_type) {
return HasStrongAuth() &&
pin_storage_prefs()->TryAuthenticatePin(pin, key_type);
bool QuickUnlockStorage::TryAuthenticatePin(const Key& key) {
return HasStrongAuth() && pin_storage_prefs()->TryAuthenticatePin(key);
}
std::string QuickUnlockStorage::CreateAuthToken(
......
......@@ -52,7 +52,7 @@ class QuickUnlockStorage : public KeyedService {
// Tries to authenticate the given pin. This will consume a pin unlock
// attempt. This always returns false if HasStrongAuth returns false.
bool TryAuthenticatePin(const std::string& pin, Key::KeyType key_type);
bool TryAuthenticatePin(const Key& key);
// Creates a new authentication token to be used by the quickSettingsPrivate
// API for authenticating requests. Resets the expiration timer and
......
......@@ -179,6 +179,11 @@ void DoMount(const base::WeakPtr<AuthAttemptState>& attempt,
// 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::Identification(attempt->user_context.GetAccountId()), auth,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment