Skip to content

Commit

Permalink
Add PinEngine and use it for webauthn pin authentication
Browse files Browse the repository at this point in the history
This CL adds the class `PinEngine` which decouples WebAuthN PIN Auth
from the QuickUnlock infrastructure in the case of auth factors enabled.
This brings us a step closer to the goal of getting rid of QuickUnlock,
while also allowing WebAuthN to use AuthSession based authentication
for PIN.

Bug: b:241256423
Change-Id: I0fcfa61dfb198c6b022e7634d3b2ce7893cc89d6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3968738
Reviewed-by: Denis Kuznetsov <antrim@chromium.org>
Commit-Queue: Elie Maamari <emaamari@google.com>
Reviewed-by: Xiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1067456}
  • Loading branch information
Elie Maamari authored and Chromium LUCI CQ committed Nov 4, 2022
1 parent 3d141c7 commit af46b49
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 11 deletions.
2 changes: 2 additions & 0 deletions chrome/browser/ui/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -2311,6 +2311,8 @@ static_library("ui") {
"ash/clipboard_util.h",
"ash/crosapi_new_window_delegate.cc",
"ash/crosapi_new_window_delegate.h",
"ash/cryptohome_pin_engine.cc",
"ash/cryptohome_pin_engine.h",
"ash/default_pinned_apps.cc",
"ash/default_pinned_apps.h",
"ash/desks/chrome_desks_templates_delegate.cc",
Expand Down
149 changes: 149 additions & 0 deletions chrome/browser/ui/ash/cryptohome_pin_engine.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/ash/cryptohome_pin_engine.h"

#include "ash/constants/ash_pref_names.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/ash/components/login/auth/auth_performer.h"
#include "components/account_id/account_id.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/known_user.h"

namespace ash {
namespace {

// Possible values for the `kQuickUnlockModeAllowlist` policy.
constexpr char kFactorsOptionAll[] = "all";
constexpr char kFactorsOptionPin[] = "PIN";

bool HasPolicyValue(const PrefService& pref_service,
CryptohomePinEngine::Purpose purpose,
const char* value) {
const base::Value::List* factors = nullptr;
switch (purpose) {
case CryptohomePinEngine::Purpose::kUnlock:
factors = &pref_service.GetList(prefs::kQuickUnlockModeAllowlist);
break;
case CryptohomePinEngine::Purpose::kWebAuthn:
factors = &pref_service.GetList(prefs::kWebAuthnFactors);
break;
default:
return false;
}
return base::Contains(*factors, base::Value(value));
}

// Check if pin is disabled for a specific purpose (so not including
// kAny) by reading the policy value.
bool IsPinDisabledByPolicySinglePurpose(const PrefService& pref_service,
CryptohomePinEngine::Purpose purpose) {
DCHECK_NE(purpose, CryptohomePinEngine::Purpose::kAny);
const bool enabled =
HasPolicyValue(pref_service, purpose, kFactorsOptionAll) ||
HasPolicyValue(pref_service, purpose, kFactorsOptionPin);
return !enabled;
}

absl::optional<bool> IsCryptohomePinDisabledByPolicy(
const AccountId& account_id,
CryptohomePinEngine::Purpose purpose) {
Profile* profile =
ash::ProfileHelper::Get()->GetProfileByAccountId(account_id);

if (!profile)
return absl::nullopt;

auto* pref_service = profile->GetPrefs();

if (!pref_service) {
return absl::nullopt;
}

if (purpose == CryptohomePinEngine::Purpose::kAny) {
return IsPinDisabledByPolicySinglePurpose(
*pref_service, CryptohomePinEngine::Purpose::kUnlock) &&
IsPinDisabledByPolicySinglePurpose(
*pref_service, CryptohomePinEngine::Purpose::kWebAuthn);
}
return IsPinDisabledByPolicySinglePurpose(*pref_service, purpose);
}

// Read the salt from local state.
std::string GetUserSalt(const AccountId& account_id) {
user_manager::KnownUser known_user(g_browser_process->local_state());
if (const std::string* salt =
known_user.FindStringPath(account_id, prefs::kQuickUnlockPinSalt)) {
return *salt;
}
return {};
}

} // namespace

CryptohomePinEngine::CryptohomePinEngine(ash::AuthPerformer* auth_performer)
: auth_performer_(auth_performer) {}

CryptohomePinEngine::~CryptohomePinEngine() = default;

void CryptohomePinEngine::IsPinAuthAvailable(
Purpose purpose,
std::unique_ptr<UserContext> user_context,
IsPinAuthAvailableCallback callback) {
auto is_pin_disabled_by_policy =
IsCryptohomePinDisabledByPolicy(user_context->GetAccountId(), purpose);

if (!is_pin_disabled_by_policy.has_value() ||
is_pin_disabled_by_policy.value()) {
std::move(callback).Run(false, std::move(user_context));
return;
}

CheckCryptohomePinFactor(std::move(user_context), std::move(callback));
}

void CryptohomePinEngine::Authenticate(
const cryptohome::RawPin& pin,
std::unique_ptr<UserContext> user_context,
AuthOperationCallback callback) {
auto salt = GetUserSalt(user_context->GetAccountId());
auth_performer_->AuthenticateWithPin(*pin, salt, std::move(user_context),
std::move(callback));
}

void CryptohomePinEngine::CheckCryptohomePinFactor(
std::unique_ptr<UserContext> user_context,
IsPinAuthAvailableCallback callback) {
auth_factor_editor_.GetAuthFactorsConfiguration(
std::move(user_context),
base::BindOnce(&CryptohomePinEngine::OnGetAuthFactorsConfiguration,
weak_factory_.GetWeakPtr(), std::move(callback)));
}

void CryptohomePinEngine::OnGetAuthFactorsConfiguration(
IsPinAuthAvailableCallback callback,
std::unique_ptr<UserContext> user_context,
absl::optional<AuthenticationError> error) {
if (error.has_value()) {
std::move(callback).Run(false, std::move(user_context));
return;
}

const auto& config = user_context->GetAuthFactorsConfiguration();
const cryptohome::AuthFactor* pin_factor =
config.FindFactorByType(cryptohome::AuthFactorType::kPin);

if (!pin_factor || pin_factor->GetPinStatus().auth_locked) {
std::move(callback).Run(false, std::move(user_context));
return;
}

std::move(callback).Run(true, std::move(user_context));
}

} // namespace ash
67 changes: 67 additions & 0 deletions chrome/browser/ui/ash/cryptohome_pin_engine.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_UI_ASH_CRYPTOHOME_PIN_ENGINE_H_
#define CHROME_BROWSER_UI_ASH_CRYPTOHOME_PIN_ENGINE_H_

#include <memory>
#include <string>

#include "base/functional/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "chromeos/ash/components/cryptohome/common_types.h"
#include "chromeos/ash/components/login/auth/auth_factor_editor.h"
#include "chromeos/ash/components/login/auth/auth_performer.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace ash {

class UserContext;

// Handles Pin related authentication operations and is the source of truth
// for the availability of Pin authentication.
class CryptohomePinEngine {
public:
enum class Purpose { kAny, kUnlock, kWebAuthn };

explicit CryptohomePinEngine(ash::AuthPerformer* auth_performer);
CryptohomePinEngine(const CryptohomePinEngine&) = delete;
CryptohomePinEngine& operator=(const CryptohomePinEngine&) = delete;
virtual ~CryptohomePinEngine();

using IsPinAuthAvailableCallback =
base::OnceCallback<void(bool, std::unique_ptr<UserContext>)>;

// Checks the availability of Pin authentication, based on things like
// policy configuration and whether or not the auth factor is set up.
void IsPinAuthAvailable(Purpose purpose,
std::unique_ptr<UserContext> user_context,
IsPinAuthAvailableCallback callback);

// The `user_context` parameter must have an associated `authsession_id`,
// acquired from a call to `AuthPerformer::StartAuthSession`.
void Authenticate(const cryptohome::RawPin& pin,
std::unique_ptr<UserContext> user_context,
AuthOperationCallback callback);

private:
// Checks for Pin factor availability and lockout status
void CheckCryptohomePinFactor(std::unique_ptr<UserContext> user_context,
IsPinAuthAvailableCallback callback);

void OnGetAuthFactorsConfiguration(IsPinAuthAvailableCallback callback,
std::unique_ptr<UserContext> user_context,
absl::optional<AuthenticationError> error);

// Non owning pointer
const base::raw_ptr<ash::AuthPerformer> auth_performer_;

ash::AuthFactorEditor auth_factor_editor_;

base::WeakPtrFactory<CryptohomePinEngine> weak_factory_{this};
};

} // namespace ash

#endif // CHROME_BROWSER_UI_ASH_CRYPTOHOME_PIN_ENGINE_H_
56 changes: 45 additions & 11 deletions chrome/browser/ui/ash/in_session_auth_dialog_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
#include "chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h"
#include "chrome/browser/ash/login/quick_unlock/quick_unlock_utils.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/cryptohome_pin_engine.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/browser_window.h"
#include "chromeos/ash/components/cryptohome/common_types.h"
#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
#include "chromeos/ash/components/login/auth/auth_performer.h"
#include "chromeos/ash/components/login/auth/public/auth_session_intent.h"
Expand Down Expand Up @@ -103,9 +105,29 @@ void InSessionAuthDialogClient::EndFingerprintAuthSession() {
void InSessionAuthDialogClient::CheckPinAuthAvailability(
const AccountId& account_id,
base::OnceCallback<void(bool)> callback) {
// PinBackend may be using cryptohome backend or prefs backend.
ash::quick_unlock::PinBackend::GetInstance()->CanAuthenticate(
account_id, ash::quick_unlock::Purpose::kWebAuthn, std::move(callback));
if (!ash::features::IsUseAuthsessionForWebAuthNEnabled()) {
// PinBackend may be using cryptohome backend or prefs backend.
ash::quick_unlock::PinBackend::GetInstance()->CanAuthenticate(
account_id, ash::quick_unlock::Purpose::kWebAuthn, std::move(callback));
return;
}

auto on_pin_availability_checked =
base::BindOnce(&InSessionAuthDialogClient::OnCheckPinAuthAvailability,
weak_factory_.GetWeakPtr(), std::move(callback));

CHECK(pin_engine_.has_value());
pin_engine_->IsPinAuthAvailable(ash::CryptohomePinEngine::Purpose::kWebAuthn,
std::move(user_context_),
std::move(on_pin_availability_checked));
}

void InSessionAuthDialogClient::OnCheckPinAuthAvailability(
base::OnceCallback<void(bool)> callback,
bool is_pin_auth_available,
std::unique_ptr<UserContext> user_context) {
user_context_ = std::move(user_context);
std::move(callback).Run(is_pin_auth_available);
}

void InSessionAuthDialogClient::StartAuthSession(
Expand All @@ -129,22 +151,23 @@ void InSessionAuthDialogClient::InvalidateAuthSession() {
if (user_context_) {
auth_performer_.InvalidateAuthSession(std::move(user_context_),
base::DoNothing());
pin_engine_.reset();
}
}

void InSessionAuthDialogClient::AuthenticateUserWithPasswordOrPin(
const std::string& password,
const std::string& secret,
bool authenticated_by_pin,
base::OnceCallback<void(bool)> callback) {
// TODO(b/156258540): Pick/validate the correct user.
const user_manager::User* const user =
user_manager::UserManager::Get()->GetActiveUser();
DCHECK(user);
auto user_context = std::make_unique<UserContext>(*user);
Key key(Key::KEY_TYPE_PASSWORD_PLAIN, std::string(), password);
Key key(Key::KEY_TYPE_PASSWORD_PLAIN, std::string(), secret);
user_context->SetIsUsingPin(authenticated_by_pin);
user_context->SetSyncPasswordData(password_manager::PasswordHashData(
user->GetAccountId().GetUserEmail(), base::UTF8ToUTF16(password),
user->GetAccountId().GetUserEmail(), base::UTF8ToUTF16(secret),
false /*force_update*/));
if (user->GetAccountId().GetAccountType() == AccountType::ACTIVE_DIRECTORY &&
(user_context->GetUserType() !=
Expand All @@ -156,7 +179,15 @@ void InSessionAuthDialogClient::AuthenticateUserWithPasswordOrPin(
DCHECK(!pending_auth_state_);
pending_auth_state_.emplace(std::move(callback));

if (authenticated_by_pin) {
if (!authenticated_by_pin) {
// TODO(yichengli): If user type is SUPERVISED, use supervised
// authenticator?
user_context->SetKey(std::move(key));
AuthenticateWithPassword(std::move(user_context), secret);
return;
}

if (!ash::features::IsUseAuthsessionForWebAuthNEnabled()) {
ash::quick_unlock::PinBackend::GetInstance()->TryAuthenticate(
std::move(user_context), std::move(key),
ash::quick_unlock::Purpose::kWebAuthn,
Expand All @@ -165,10 +196,11 @@ void InSessionAuthDialogClient::AuthenticateUserWithPasswordOrPin(
return;
}

// TODO(yichengli): If user type is SUPERVISED, use supervised authenticator?

user_context->SetKey(std::move(key));
AuthenticateWithPassword(std::move(user_context), password);
pin_engine_->Authenticate(
cryptohome::RawPin(secret), std::move(user_context_),
base::BindOnce(&InSessionAuthDialogClient::OnAuthVerified,
weak_factory_.GetWeakPtr(),
/*authenticated_by_password=*/false));
}

void InSessionAuthDialogClient::OnPinAttemptDone(
Expand Down Expand Up @@ -207,6 +239,7 @@ void InSessionAuthDialogClient::AuthenticateWithPassword(
// on other UserDataAuth dbus calls that involve the auth_session_id stored
// in this `user_context`.
CHECK(user_context_);

const cryptohome::AuthFactor* password_factor =
user_context_->GetAuthFactorsData().FindOnlinePasswordFactor();
if (!password_factor) {
Expand Down Expand Up @@ -247,6 +280,7 @@ void InSessionAuthDialogClient::OnAuthSessionStarted(

// Take temporary ownership of user_context to pass on later.
user_context_ = std::move(user_context);
pin_engine_.emplace(&auth_performer_);
std::move(callback).Run(true);
}

Expand Down
11 changes: 11 additions & 0 deletions chrome/browser/ui/ash/in_session_auth_dialog_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "base/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/ui/ash/cryptohome_pin_engine.h"
#include "chromeos/ash/components/login/auth/auth_performer.h"
#include "chromeos/ash/components/login/auth/auth_status_consumer.h"
#include "chromeos/ash/components/login/auth/extended_authenticator.h"
Expand Down Expand Up @@ -115,6 +116,14 @@ class InSessionAuthDialogClient : public ash::InSessionAuthDialogClient,
base::OnceCallback<void(bool, ash::FingerprintState)> callback,
user_data_auth::CryptohomeErrorCode error);

// Passed as a callback to `CryptohomePinEngine::InPinAuthAvailable`
// Takes back ownership of the `user_context` that was borrowed by
// `CryptohomePinEngine` and notifies callers of pin availability status.
void OnCheckPinAuthAvailability(
base::OnceCallback<void(bool)> callback,
bool is_pin_auth_available,
std::unique_ptr<ash::UserContext> user_context);

// Used to authenticate the user to unlock supervised users.
scoped_refptr<ash::ExtendedAuthenticator> extended_authenticator_;

Expand All @@ -124,6 +133,8 @@ class InSessionAuthDialogClient : public ash::InSessionAuthDialogClient,
// Used to start and authenticate auth sessions.
ash::AuthPerformer auth_performer_;

absl::optional<ash::CryptohomePinEngine> pin_engine_;

std::unique_ptr<ash::UserContext> user_context_;

base::WeakPtrFactory<InSessionAuthDialogClient> weak_factory_{this};
Expand Down

0 comments on commit af46b49

Please sign in to comment.