blob: 1ccc228686630a14bfd741afe526f784387e81f2 [file] [log] [blame]
// Copyright 2016 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/extensions/quick_unlock_private/quick_unlock_private_api.h"
#include <algorithm>
#include <string>
#include <utility>
#include "ash/public/cpp/ash_pref_names.h"
#include "base/stl_util.h"
#include "base/task/post_task.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_service.h"
#include "chrome/browser/chromeos/login/quick_unlock/auth_token.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/quick_unlock/quick_unlock_utils.h"
#include "chrome/browser/chromeos/login/supervised/supervised_user_authentication.h"
#include "chrome/browser/chromeos/login/users/chrome_user_manager.h"
#include "chrome/browser/chromeos/login/users/supervised_user_manager.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/pref_names.h"
#include "chromeos/login/auth/extended_authenticator.h"
#include "chromeos/login/auth/user_context.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/event_router.h"
namespace extensions {
namespace quick_unlock_private = api::quick_unlock_private;
namespace SetModes = quick_unlock_private::SetModes;
namespace GetActiveModes = quick_unlock_private::GetActiveModes;
namespace CheckCredential = quick_unlock_private::CheckCredential;
namespace GetCredentialRequirements =
quick_unlock_private::GetCredentialRequirements;
namespace GetAvailableModes = quick_unlock_private::GetAvailableModes;
namespace OnActiveModesChanged = quick_unlock_private::OnActiveModesChanged;
using CredentialProblem = quick_unlock_private::CredentialProblem;
using CredentialCheck = quick_unlock_private::CredentialCheck;
using CredentialRequirements = quick_unlock_private::CredentialRequirements;
using QuickUnlockMode = quick_unlock_private::QuickUnlockMode;
using AuthToken = chromeos::quick_unlock::AuthToken;
using QuickUnlockModeList = std::vector<QuickUnlockMode>;
using QuickUnlockStorage = chromeos::quick_unlock::QuickUnlockStorage;
using ActiveModeCallback = base::OnceCallback<void(const QuickUnlockModeList&)>;
namespace {
const char kModesAndCredentialsLengthMismatch[] =
"|modes| and |credentials| must have the same number of elements";
const char kMultipleModesNotSupported[] =
"At most one quick unlock mode can be active.";
const char kPinDisabledByPolicy[] = "PIN unlock has been disabled by policy";
const char kInvalidPIN[] = "Invalid PIN.";
const char kInvalidCredential[] = "Invalid credential.";
const char kWeakCredential[] = "Weak credential.";
const char kPasswordIncorrect[] = "Incorrect Password.";
const char kAuthTokenExpired[] = "Authentication token expired.";
const char kAuthTokenInvalid[] = "Authentication token invalid.";
// PINs greater in length than |kMinLengthForWeakPin| will be checked for
// weakness.
constexpr size_t kMinLengthForNonWeakPin = 2U;
// A list of the most commmonly used PINs, whose digits are not all the same,
// increasing or decreasing. This list is taken from
// www.datagenetics.com/blog/september32012/.
constexpr const char* kMostCommonPins[] = {"1212", "1004", "2000", "6969",
"1122", "1313", "2001", "1010"};
// Returns the active set of quick unlock modes.
void ComputeActiveModes(Profile* profile, ActiveModeCallback result) {
user_manager::User* user =
chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
chromeos::quick_unlock::PinBackend::GetInstance()->IsSet(
user->GetAccountId(),
base::BindOnce(
[](ActiveModeCallback result, bool is_set) {
QuickUnlockModeList modes;
if (is_set)
modes.push_back(quick_unlock_private::QUICK_UNLOCK_MODE_PIN);
std::move(result).Run(modes);
},
std::move(result)));
}
// Returns true if |a| and |b| contain the same elements. The elements do not
// need to be in the same order.
bool AreModesEqual(const QuickUnlockModeList& a, const QuickUnlockModeList& b) {
if (a.size() != b.size())
return false;
// This is a slow comparison algorithm, but the number of entries in |a| and
// |b| will always be very low (0-3 items) so it doesn't matter.
for (size_t i = 0; i < a.size(); ++i) {
if (!base::ContainsValue(b, a[i]))
return false;
}
return true;
}
bool IsPinNumeric(const std::string& pin) {
return std::all_of(pin.begin(), pin.end(), ::isdigit);
}
// Reads and sanitizes the pin length policy.
// Returns the minimum and maximum required pin lengths.
// - minimum must be at least 1.
// - maximum must be at least |min_length|, or 0.
std::pair<int, int> GetSanitizedPolicyPinMinMaxLength(
PrefService* pref_service) {
int min_length =
std::max(pref_service->GetInteger(prefs::kPinUnlockMinimumLength), 1);
int max_length = pref_service->GetInteger(prefs::kPinUnlockMaximumLength);
max_length = max_length > 0 ? std::max(max_length, min_length) : 0;
DCHECK_GE(min_length, 1);
DCHECK_GE(max_length, 0);
return std::make_pair(min_length, max_length);
}
// Checks whether a given |pin| has any problems given the PIN min/max policies
// in |pref_service|. Returns CREDENTIAL_PROBLEM_NONE if |pin| has no problems,
// or another CREDENTIAL_PROBLEM_ enum value to indicate the detected problem.
CredentialProblem GetCredentialProblemForPin(const std::string& pin,
PrefService* pref_service) {
int min_length;
int max_length;
std::tie(min_length, max_length) =
GetSanitizedPolicyPinMinMaxLength(pref_service);
// Check if the PIN is shorter than the minimum specified length.
if (pin.size() < static_cast<size_t>(min_length))
return CredentialProblem::CREDENTIAL_PROBLEM_TOO_SHORT;
// If the maximum specified length is zero, there is no maximum length.
// Otherwise check if the PIN is longer than the maximum specified length.
if (max_length != 0 && pin.size() > static_cast<size_t>(max_length))
return CredentialProblem::CREDENTIAL_PROBLEM_TOO_LONG;
return CredentialProblem::CREDENTIAL_PROBLEM_NONE;
}
// Checks if a given |pin| is weak or not. A PIN is considered weak if it:
// a) is on this list - www.datagenetics.com/blog/september32012/
// b) has all the same digits
// c) each digit is one larger than the previous digit
// d) each digit is one smaller than the previous digit
// Note: A 9 followed by a 0 is not considered increasing, and a 0 followed by
// a 9 is not considered decreasing.
bool IsPinDifficultEnough(const std::string& pin) {
// If the pin length is |kMinLengthForNonWeakPin| or less, there is no need to
// check for same character and increasing pin.
if (pin.size() <= kMinLengthForNonWeakPin)
return true;
// Check if it is on the list of most common PINs.
if (base::ContainsValue(kMostCommonPins, pin))
return false;
// Check for same digits, increasing and decreasing PIN simultaneously.
bool is_same = true;
// TODO(sammiequon): Should longer PINs (5+) be still subjected to this?
bool is_increasing = true;
bool is_decreasing = true;
for (size_t i = 1; i < pin.length(); ++i) {
const char previous = pin[i - 1];
const char current = pin[i];
is_same = is_same && (current == previous);
is_increasing = is_increasing && (current == previous + 1);
is_decreasing = is_decreasing && (current == previous - 1);
}
// PIN is considered weak if any of these conditions is met.
if (is_same || is_increasing || is_decreasing)
return false;
return true;
}
Profile* GetActiveProfile(content::BrowserContext* browser_context) {
Profile* profile = Profile::FromBrowserContext(browser_context);
// When OOBE continues in-session as Furst Run UI, it is still executed
// under Sign-In profile.
if (chromeos::ProfileHelper::IsSigninProfile(profile))
return ProfileManager::GetPrimaryUserProfile();
return profile;
}
} // namespace
// quickUnlockPrivate.getAuthToken
QuickUnlockPrivateGetAuthTokenFunction::QuickUnlockPrivateGetAuthTokenFunction()
: chrome_details_(this) {}
QuickUnlockPrivateGetAuthTokenFunction::
~QuickUnlockPrivateGetAuthTokenFunction() {
if (extended_authenticator_)
extended_authenticator_->SetConsumer(nullptr);
}
void QuickUnlockPrivateGetAuthTokenFunction::
SetAuthenticatorAllocatorForTesting(
const QuickUnlockPrivateGetAuthTokenFunction::AuthenticatorAllocator&
allocator) {
authenticator_allocator_ = allocator;
}
ExtensionFunction::ResponseAction
QuickUnlockPrivateGetAuthTokenFunction::Run() {
std::unique_ptr<quick_unlock_private::GetAuthToken::Params> params =
quick_unlock_private::GetAuthToken::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(params);
const user_manager::User* const user =
chromeos::ProfileHelper::Get()->GetUserByProfile(
GetActiveProfile(browser_context()));
chromeos::UserContext user_context(*user);
user_context.SetKey(chromeos::Key(params->account_password));
// Alter |user_context| if the user is supervised.
if (user->GetType() == user_manager::USER_TYPE_SUPERVISED) {
user_context = chromeos::ChromeUserManager::Get()
->GetSupervisedUserManager()
->GetAuthentication()
->TransformKey(user_context);
}
// Lazily allocate the authenticator. We do this here, instead of in the ctor,
// so that tests can install a fake.
DCHECK(!extended_authenticator_);
if (authenticator_allocator_)
extended_authenticator_ = authenticator_allocator_.Run(this);
else
extended_authenticator_ = chromeos::ExtendedAuthenticator::Create(this);
// The extension function needs to stay alive while the authenticator is
// running the password check. Add a ref before the authenticator starts, and
// remove the ref after it invokes one of the OnAuth* callbacks. The PostTask
// call applies ref management to the extended_authenticator_ instance and not
// to the extension function instance, which is why the manual ref management
// is needed.
AddRef();
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(&chromeos::ExtendedAuthenticator::AuthenticateToCheck,
extended_authenticator_.get(), user_context,
base::Closure()));
return RespondLater();
}
void QuickUnlockPrivateGetAuthTokenFunction::OnAuthFailure(
const chromeos::AuthFailure& error) {
Respond(Error(kPasswordIncorrect));
Release(); // Balanced in Run().
}
void QuickUnlockPrivateGetAuthTokenFunction::OnAuthSuccess(
const chromeos::UserContext& user_context) {
auto result = std::make_unique<quick_unlock_private::TokenInfo>();
Profile* profile = GetActiveProfile(browser_context());
QuickUnlockStorage* quick_unlock_storage =
chromeos::quick_unlock::QuickUnlockFactory::GetForProfile(profile);
result->token = quick_unlock_storage->CreateAuthToken(user_context);
result->lifetime_seconds = AuthToken::kTokenExpirationSeconds;
// The user has successfully authenticated so we should reset pin/fingerprint
// attempt counts.
quick_unlock_storage->pin_storage_prefs()->ResetUnlockAttemptCount();
quick_unlock_storage->fingerprint_storage()->ResetUnlockAttemptCount();
Respond(ArgumentList(
quick_unlock_private::GetAuthToken::Results::Create(*result)));
Release(); // Balanced in Run().
}
// quickUnlockPrivate.setLockScreenEnabled
QuickUnlockPrivateSetLockScreenEnabledFunction::
QuickUnlockPrivateSetLockScreenEnabledFunction()
: chrome_details_(this) {}
QuickUnlockPrivateSetLockScreenEnabledFunction::
~QuickUnlockPrivateSetLockScreenEnabledFunction() {}
ExtensionFunction::ResponseAction
QuickUnlockPrivateSetLockScreenEnabledFunction::Run() {
auto params =
quick_unlock_private::SetLockScreenEnabled::Params::Create(*args_);
Profile* profile = GetActiveProfile(browser_context());
QuickUnlockStorage* quick_unlock_storage =
chromeos::quick_unlock::QuickUnlockFactory::GetForProfile(profile);
if (quick_unlock_storage->GetAuthTokenExpired())
return RespondNow(Error(kAuthTokenExpired));
if (params->token != quick_unlock_storage->GetAuthToken())
return RespondNow(Error(kAuthTokenInvalid));
profile->GetPrefs()->SetBoolean(ash::prefs::kEnableAutoScreenLock,
params->enabled);
return RespondNow(ArgumentList(
quick_unlock_private::SetLockScreenEnabled::Results::Create()));
}
// quickUnlockPrivate.getAvailableModes
QuickUnlockPrivateGetAvailableModesFunction::
QuickUnlockPrivateGetAvailableModesFunction()
: chrome_details_(this) {}
QuickUnlockPrivateGetAvailableModesFunction::
~QuickUnlockPrivateGetAvailableModesFunction() {}
ExtensionFunction::ResponseAction
QuickUnlockPrivateGetAvailableModesFunction::Run() {
QuickUnlockModeList modes;
if (!chromeos::quick_unlock::IsPinDisabledByPolicy(
GetActiveProfile(browser_context())->GetPrefs())) {
modes.push_back(quick_unlock_private::QUICK_UNLOCK_MODE_PIN);
}
return RespondNow(ArgumentList(GetAvailableModes::Results::Create(modes)));
}
// quickUnlockPrivate.getActiveModes
QuickUnlockPrivateGetActiveModesFunction::
QuickUnlockPrivateGetActiveModesFunction()
: chrome_details_(this) {}
QuickUnlockPrivateGetActiveModesFunction::
~QuickUnlockPrivateGetActiveModesFunction() = default;
ExtensionFunction::ResponseAction
QuickUnlockPrivateGetActiveModesFunction::Run() {
ComputeActiveModes(
GetActiveProfile(browser_context()),
base::BindOnce(
&QuickUnlockPrivateGetActiveModesFunction::OnGetActiveModes, this));
return RespondLater();
}
void QuickUnlockPrivateGetActiveModesFunction::OnGetActiveModes(
const std::vector<api::quick_unlock_private::QuickUnlockMode>& modes) {
Respond(ArgumentList(GetActiveModes::Results::Create(modes)));
}
// quickUnlockPrivate.checkCredential
QuickUnlockPrivateCheckCredentialFunction::
QuickUnlockPrivateCheckCredentialFunction() {}
QuickUnlockPrivateCheckCredentialFunction::
~QuickUnlockPrivateCheckCredentialFunction() {}
ExtensionFunction::ResponseAction
QuickUnlockPrivateCheckCredentialFunction::Run() {
std::unique_ptr<CheckCredential::Params> params_ =
CheckCredential::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(params_);
auto result = std::make_unique<CredentialCheck>();
// Only handles pins for now.
if (params_->mode != QuickUnlockMode::QUICK_UNLOCK_MODE_PIN)
return RespondNow(ArgumentList(CheckCredential::Results::Create(*result)));
const std::string& credential = params_->credential;
Profile* profile = GetActiveProfile(browser_context());
PrefService* pref_service = profile->GetPrefs();
bool allow_weak = pref_service->GetBoolean(prefs::kPinUnlockWeakPinsAllowed);
// Check and return the problems.
std::vector<CredentialProblem>& warnings = result->warnings;
std::vector<CredentialProblem>& errors = result->errors;
if (!IsPinNumeric(credential))
errors.push_back(CredentialProblem::CREDENTIAL_PROBLEM_CONTAINS_NONDIGIT);
CredentialProblem length_problem =
GetCredentialProblemForPin(credential, pref_service);
if (length_problem != CredentialProblem::CREDENTIAL_PROBLEM_NONE)
errors.push_back(length_problem);
if (!IsPinDifficultEnough(credential)) {
auto& log = allow_weak ? warnings : errors;
log.push_back(CredentialProblem::CREDENTIAL_PROBLEM_TOO_WEAK);
}
return RespondNow(ArgumentList(CheckCredential::Results::Create(*result)));
}
QuickUnlockPrivateGetCredentialRequirementsFunction::
QuickUnlockPrivateGetCredentialRequirementsFunction() {}
QuickUnlockPrivateGetCredentialRequirementsFunction::
~QuickUnlockPrivateGetCredentialRequirementsFunction() {}
ExtensionFunction::ResponseAction
QuickUnlockPrivateGetCredentialRequirementsFunction::Run() {
std::unique_ptr<GetCredentialRequirements::Params> params_ =
GetCredentialRequirements::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(params_);
auto result = std::make_unique<CredentialRequirements>();
std::tie(result->min_length, result->max_length) =
GetSanitizedPolicyPinMinMaxLength(
GetActiveProfile(browser_context())->GetPrefs());
return RespondNow(
ArgumentList(GetCredentialRequirements::Results::Create(*result)));
}
// quickUnlockPrivate.setModes
QuickUnlockPrivateSetModesFunction::QuickUnlockPrivateSetModesFunction()
: chrome_details_(this) {}
QuickUnlockPrivateSetModesFunction::~QuickUnlockPrivateSetModesFunction() =
default;
void QuickUnlockPrivateSetModesFunction::SetModesChangedEventHandlerForTesting(
const ModesChangedEventHandler& handler) {
modes_changed_handler_ = handler;
}
ExtensionFunction::ResponseAction QuickUnlockPrivateSetModesFunction::Run() {
params_ = SetModes::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(params_);
if (params_->modes.size() != params_->credentials.size())
return RespondNow(Error(kModesAndCredentialsLengthMismatch));
if (params_->modes.size() > 1)
return RespondNow(Error(kMultipleModesNotSupported));
Profile* profile = GetActiveProfile(browser_context());
QuickUnlockStorage* quick_unlock_storage =
chromeos::quick_unlock::QuickUnlockFactory::GetForProfile(profile);
if (quick_unlock_storage->GetAuthTokenExpired())
return RespondNow(Error(kAuthTokenExpired));
if (params_->token != quick_unlock_storage->GetAuthToken())
return RespondNow(Error(kAuthTokenInvalid));
// Verify every credential is valid based on policies.
PrefService* pref_service = GetActiveProfile(browser_context())->GetPrefs();
// Do not allow setting a PIN if it is disabled by policy. It is disabled
// on the UI, but users can still reach here via dev tools.
for (size_t i = 0; i < params_->modes.size(); ++i) {
if (params_->modes[i] == QuickUnlockMode::QUICK_UNLOCK_MODE_PIN &&
chromeos::quick_unlock::IsPinDisabledByPolicy(pref_service)) {
return RespondNow(Error(kPinDisabledByPolicy));
}
}
// Verify every credential is valid based on policies.
bool allow_weak = pref_service->GetBoolean(prefs::kPinUnlockWeakPinsAllowed);
for (size_t i = 0; i < params_->modes.size(); ++i) {
if (params_->credentials[i].empty())
continue;
if (params_->modes[i] != QuickUnlockMode::QUICK_UNLOCK_MODE_PIN)
continue;
if (!IsPinNumeric(params_->credentials[i]))
return RespondNow(Error(kInvalidPIN));
CredentialProblem problem =
GetCredentialProblemForPin(params_->credentials[i], pref_service);
if (problem != CredentialProblem::CREDENTIAL_PROBLEM_NONE)
return RespondNow(Error(kInvalidCredential));
if (!allow_weak && !IsPinDifficultEnough(params_->credentials[i]))
return RespondNow(Error(kWeakCredential));
}
ComputeActiveModes(
GetActiveProfile(browser_context()),
base::BindOnce(&QuickUnlockPrivateSetModesFunction::OnGetActiveModes,
this));
return RespondLater();
}
void QuickUnlockPrivateSetModesFunction::OnGetActiveModes(
const std::vector<QuickUnlockMode>& initial_modes) {
initial_modes_ = initial_modes;
// This function is setup so it is easy to add another quick unlock mode while
// following all of the invariants, which are:
//
// 1: If an unlock type is not specified, it should be deactivated.
// 2: If a credential for an unlock type is empty, it should not be touched.
// 3: Otherwise, the credential should be set to the new value.
bool update_pin = true;
std::string pin_credential;
// Compute needed changes.
DCHECK_EQ(params_->credentials.size(), params_->modes.size());
for (size_t i = 0; i < params_->modes.size(); ++i) {
const QuickUnlockMode mode = params_->modes[i];
const std::string& credential = params_->credentials[i];
if (mode == quick_unlock_private::QUICK_UNLOCK_MODE_PIN) {
update_pin = !credential.empty();
pin_credential = credential;
}
}
// Apply changes.
if (update_pin) {
Profile* profile = GetActiveProfile(browser_context());
user_manager::User* user =
chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
if (pin_credential.empty()) {
chromeos::quick_unlock::PinBackend::GetInstance()->Remove(
user->GetAccountId(), params_->token,
base::BindOnce(
&QuickUnlockPrivateSetModesFunction::PinBackendCallComplete,
this));
} else {
chromeos::quick_unlock::PinBackend::GetInstance()->Set(
user->GetAccountId(), params_->token, pin_credential,
base::BindOnce(
&QuickUnlockPrivateSetModesFunction::PinBackendCallComplete,
this));
}
} else {
// No changes to apply. Call result directly.
ModeChangeComplete(initial_modes_);
}
}
void QuickUnlockPrivateSetModesFunction::PinBackendCallComplete(bool result) {
ComputeActiveModes(
GetActiveProfile(browser_context()),
base::BindOnce(&QuickUnlockPrivateSetModesFunction::ModeChangeComplete,
this));
}
void QuickUnlockPrivateSetModesFunction::ModeChangeComplete(
const std::vector<QuickUnlockMode>& updated_modes) {
if (!AreModesEqual(initial_modes_, updated_modes))
FireEvent(updated_modes);
const user_manager::User* const user =
chromeos::ProfileHelper::Get()->GetUserByProfile(
GetActiveProfile(browser_context()));
const chromeos::UserContext user_context(*user);
chromeos::EasyUnlockService::Get(GetActiveProfile(browser_context()))
->HandleUserReauth(user_context);
Respond(ArgumentList(SetModes::Results::Create()));
}
// Triggers a quickUnlockPrivate.onActiveModesChanged change event.
void QuickUnlockPrivateSetModesFunction::FireEvent(
const QuickUnlockModeList& modes) {
// Allow unit tests to override how events are raised/handled.
if (modes_changed_handler_) {
modes_changed_handler_.Run(modes);
return;
}
std::unique_ptr<base::ListValue> args = OnActiveModesChanged::Create(modes);
auto event = std::make_unique<Event>(
events::QUICK_UNLOCK_PRIVATE_ON_ACTIVE_MODES_CHANGED,
OnActiveModesChanged::kEventName, std::move(args));
EventRouter::Get(browser_context())->BroadcastEvent(std::move(event));
}
} // namespace extensions