chromium/chrome/browser/device_reauth/win/authenticator_win.cc

// 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/device_reauth/win/authenticator_win.h"

#include <objbase.h>

#include <UserConsentVerifierInterop.h>
#include <windows.foundation.h>
#include <windows.security.credentials.ui.h>
#include <windows.storage.streams.h>
#include <wrl/client.h>
#include <wrl/event.h>

#include <string>
#include <utility>

#include "authenticator_win.h"
#include "base/barrier_callback.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_thread_priority.h"
#include "base/win/core_winrt_util.h"
#include "base/win/hstring_reference.h"
#include "base/win/post_async_results.h"
#include "base/win/registry.h"
#include "base/win/scoped_hstring.h"
#include "base/win/scoped_winrt_initializer.h"
#include "base/win/windows_types.h"
#include "base/win/windows_version.h"
#include "chrome/browser/password_manager/password_manager_util_win.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "components/password_manager/core/browser/features/password_features.h"
#include "ui/aura/window.h"
#include "ui/views/win/hwnd_util.h"

namespace {

// Enum specifying possible states of Windows Hello authentication. These
// values are persisted to logs. Entries should not be renumbered and numeric
// values should never be reused.
enum class AuthenticationStateWin {
  kStarted = 0,
  kFinished = 1,
  kMaxValue = kFinished,
};

using AvailabilityCallback = AuthenticatorWinInterface::AvailabilityCallback;
using ABI::Windows::Foundation::IAsyncOperation;
using ABI::Windows::Security::Credentials::UI::IUserConsentVerifierStatics;
using ABI::Windows::Security::Credentials::UI::UserConsentVerificationResult;
using ABI::Windows::Security::Credentials::UI::UserConsentVerifierAvailability;
using enum ABI::Windows::Security::Credentials::UI::
    UserConsentVerifierAvailability;
using ABI::Windows::Security::Credentials::UI::UserConsentVerificationResult;
using enum ABI::Windows::Security::Credentials::UI::
    UserConsentVerificationResult;
using Microsoft::WRL::ComPtr;

BiometricAuthenticationStatusWin ConvertUserConsentVerifierAvailability(
    UserConsentVerifierAvailability availability) {
  switch (availability) {
    case UserConsentVerifierAvailability_Available:
      return BiometricAuthenticationStatusWin::kAvailable;
    case UserConsentVerifierAvailability_DeviceBusy:
      return BiometricAuthenticationStatusWin::kDeviceBusy;
    case UserConsentVerifierAvailability_DeviceNotPresent:
      return BiometricAuthenticationStatusWin::kDeviceNotPresent;
    case UserConsentVerifierAvailability_DisabledByPolicy:
      return BiometricAuthenticationStatusWin::kDisabledByPolicy;
    case UserConsentVerifierAvailability_NotConfiguredForUser:
      return BiometricAuthenticationStatusWin::kNotConfiguredForUser;
    default:
      return BiometricAuthenticationStatusWin::kUnknown;
  }
}

AuthenticationResultStatusWin ConvertUserConsentVerificationResult(
    UserConsentVerificationResult result) {
  switch (result) {
    case UserConsentVerificationResult_Verified:
      return AuthenticationResultStatusWin::kVerified;
    case UserConsentVerificationResult_DeviceNotPresent:
      return AuthenticationResultStatusWin::kDeviceNotPresent;
    case UserConsentVerificationResult_NotConfiguredForUser:
      return AuthenticationResultStatusWin::kNotConfiguredForUser;
    case UserConsentVerificationResult_DisabledByPolicy:
      return AuthenticationResultStatusWin::kDisabledByPolicy;
    case UserConsentVerificationResult_DeviceBusy:
      return AuthenticationResultStatusWin::kDeviceBusy;
    case UserConsentVerificationResult_RetriesExhausted:
      return AuthenticationResultStatusWin::kRetriesExhausted;
    case UserConsentVerificationResult_Canceled:
      return AuthenticationResultStatusWin::kCanceled;
    default:
      return AuthenticationResultStatusWin::kUnknown;
  }
}

void RecordWindowsHelloAuthenticationResult(
    AuthenticationResultStatusWin result) {
  base::UmaHistogramEnumeration(
      "PasswordManager.RequestVerificationAsyncResult", result);
}

void RecordAuthenticationState(AuthenticationStateWin state) {
  base::UmaHistogramEnumeration("PasswordManager.AuthenticationStateWin",
                                state);
}

void RecordAuthenticationAsyncOpFailureReson(HRESULT hr) {
  base::UmaHistogramSparse("PasswordManager.AuthenticationAsyncOpFailureReson",
                           hr);
}

void ReturnAvailabilityValue(AvailabilityCallback callback,
                             UserConsentVerifierAvailability availability) {
  std::move(callback).Run(ConvertUserConsentVerifierAvailability(availability));
}

void OnAvailabilityReceived(scoped_refptr<base::SequencedTaskRunner> thread,
                            AvailabilityCallback callback,
                            UserConsentVerifierAvailability availability) {
  thread->PostTask(FROM_HERE,
                   base::BindOnce(&ReturnAvailabilityValue, std::move(callback),
                                  availability));
}

void ReportCantCheckAvailability(
    scoped_refptr<base::SequencedTaskRunner> thread,
    AvailabilityCallback callback) {
  thread->PostTask(FROM_HERE,
                   base::BindOnce(std::move(callback),
                                  BiometricAuthenticationStatusWin::kUnknown));
}

bool VectorToFirstElement(std::vector<bool> result) {
  CHECK_EQ(result.size(), 1u);
  return result[0];
}

// Asks operating system if user has configured and enabled Windows Hello on
// their machine. Runs `callback` on `thread`.
void GetBiometricAvailabilityFromWindows(
    AvailabilityCallback callback,
    scoped_refptr<base::SequencedTaskRunner> thread) {
  // Mitigate the issues caused by loading DLLs on a background thread
  // (http://crbug/973868).
  SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();

  ComPtr<IUserConsentVerifierStatics> factory;
  HRESULT hr = base::win::GetActivationFactory<
      IUserConsentVerifierStatics,
      RuntimeClass_Windows_Security_Credentials_UI_UserConsentVerifier>(
      &factory);
  if (FAILED(hr)) {
    ReportCantCheckAvailability(thread, std::move(callback));
    return;
  }
  ComPtr<IAsyncOperation<UserConsentVerifierAvailability>> async_op;
  hr = factory->CheckAvailabilityAsync(&async_op);
  if (FAILED(hr)) {
    ReportCantCheckAvailability(thread, std::move(callback));
    return;
  }

  base::win::PostAsyncResults(
      std::move(async_op),
      base::BindOnce(&OnAvailabilityReceived, thread, std::move(callback)));
}

void AuthenticateWithLegacyApi(const std::u16string& message,
                               base::OnceCallback<void(bool)> result_callback) {
  Browser* browser = chrome::FindLastActive();
  if (!browser) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(result_callback), /*success=*/false));
    return;
  }
  gfx::NativeWindow window = browser->window()->GetNativeWindow();

  base::SequencedTaskRunner::GetCurrentDefault()->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&password_manager_util_win::AuthenticateUser, window,
                     message),
      std::move(result_callback));
}

void OnAuthenticationReceived(base::OnceCallback<void(bool)> callback,
                              const std::u16string& message,
                              UserConsentVerificationResult result) {
  AuthenticationResultStatusWin authentication_result =
      ConvertUserConsentVerificationResult(result);
  RecordWindowsHelloAuthenticationResult(authentication_result);

  switch (authentication_result) {
    case AuthenticationResultStatusWin::kVerified:
      std::move(callback).Run(/*success=*/true);
      return;
    case AuthenticationResultStatusWin::kCanceled:
    case AuthenticationResultStatusWin::kRetriesExhausted:
      std::move(callback).Run(/*success=*/false);
      return;
    case AuthenticationResultStatusWin::kDeviceNotPresent:
    case AuthenticationResultStatusWin::kNotConfiguredForUser:
    case AuthenticationResultStatusWin::kDisabledByPolicy:
    case AuthenticationResultStatusWin::kDeviceBusy:
    case AuthenticationResultStatusWin::kUnknown:
      // Windows Hello is not available so there should be a fallback to the old
      // API.
      AuthenticateWithLegacyApi(message, std::move(callback));
      return;
    case AuthenticationResultStatusWin::kFailedToCallAPI:
    case AuthenticationResultStatusWin::kFailedToCreateFactory:
    case AuthenticationResultStatusWin::kFailedToPostTask:
    case AuthenticationResultStatusWin::kAsyncOperationFailed:
    case AuthenticationResultStatusWin::kFailedToFindBrowser:
    case AuthenticationResultStatusWin::kFailedToFindHWNDForNativeWindow:
      // This values are not returned by UserConsentVerifier API.
      NOTREACHED();
  }
}

void OnAuthenticationAsyncOpFail(
    base::OnceCallback<void(bool)> callback,
    const std::u16string& message,
    HRESULT hr) {
  RecordWindowsHelloAuthenticationResult(
      AuthenticationResultStatusWin::kAsyncOperationFailed);
  RecordAuthenticationAsyncOpFailureReson(hr);

  AuthenticateWithLegacyApi(message, std::move(callback));
}

// TODO(b/349728186): Cleanup after Win11 solution is launched.
void PerformWindowsHelloAuthenticationAsync(
    base::OnceCallback<void(bool)> callback,
    const std::u16string& message) {
  ComPtr<IUserConsentVerifierStatics> factory;
  HRESULT hr = base::win::GetActivationFactory<
      IUserConsentVerifierStatics,
      RuntimeClass_Windows_Security_Credentials_UI_UserConsentVerifier>(
      &factory);
  if (FAILED(hr)) {
    RecordWindowsHelloAuthenticationResult(
        AuthenticationResultStatusWin::kFailedToCreateFactory);
    AuthenticateWithLegacyApi(message, std::move(callback));
    return;
  }
  ComPtr<IAsyncOperation<UserConsentVerificationResult>> async_op;
  hr = factory->RequestVerificationAsync(
      base::win::HStringReference(base::UTF16ToWide(message).c_str()).Get(),
      &async_op);
  if (FAILED(hr)) {
    RecordWindowsHelloAuthenticationResult(
        AuthenticationResultStatusWin::kFailedToCallAPI);
    AuthenticateWithLegacyApi(message, std::move(callback));
    return;
  }

  // In order to pass the callback to both the PostAsyncHandlers call and
  // to the AuthenticateWithLegacyApi (if the former fails to post the task) a
  // RepeatingCallback is needed. The callback will be executed at most once.
  auto barrier_callback = base::BarrierCallback<bool>(
      1, base::BindOnce(&VectorToFirstElement).Then(std::move(callback)));

  hr = base::win::PostAsyncHandlers(
      async_op.Get(),
      base::BindOnce(&OnAuthenticationReceived, barrier_callback, message),
      base::BindOnce(&OnAuthenticationAsyncOpFail, barrier_callback, message));

  if (FAILED(hr)) {
    RecordWindowsHelloAuthenticationResult(
        AuthenticationResultStatusWin::kFailedToPostTask);
    AuthenticateWithLegacyApi(message, barrier_callback);
    return;
  }
}

void PerformInteropWindowsHelloAuthenticationAsync(
    base::OnceCallback<void(bool)> callback,
    const std::u16string& message) {
  ComPtr<IUserConsentVerifierInterop> factory;
  HRESULT hr = base::win::GetActivationFactory<
      IUserConsentVerifierInterop,
      RuntimeClass_Windows_Security_Credentials_UI_UserConsentVerifier>(
      &factory);
  if (FAILED(hr)) {
    RecordWindowsHelloAuthenticationResult(
        AuthenticationResultStatusWin::kFailedToCreateFactory);
    AuthenticateWithLegacyApi(message, std::move(callback));
    return;
  }
  ComPtr<IAsyncOperation<UserConsentVerificationResult>> async_op;

  Browser* browser = chrome::FindLastActive();
  if (!browser) {
    RecordWindowsHelloAuthenticationResult(
        AuthenticationResultStatusWin::kFailedToFindBrowser);
    AuthenticateWithLegacyApi(message, std::move(callback));
    return;
  }

  HWND hwnd = views::HWNDForNativeWindow(browser->window()->GetNativeWindow());
  if (!hwnd) {
    RecordWindowsHelloAuthenticationResult(
        AuthenticationResultStatusWin::kFailedToFindHWNDForNativeWindow);
    AuthenticateWithLegacyApi(message, std::move(callback));
    return;
  }

  hr = factory->RequestVerificationForWindowAsync(
      hwnd,
      base::win::HStringReference(base::UTF16ToWide(message).c_str()).Get(),
      __uuidof(IAsyncOperation<UserConsentVerificationResult>), &async_op);

  if (FAILED(hr)) {
    RecordWindowsHelloAuthenticationResult(
        AuthenticationResultStatusWin::kFailedToCallAPI);
    AuthenticateWithLegacyApi(message, std::move(callback));
    return;
  }

  // In order to pass the callback to both the PostAsyncHandlers call and
  // to the AuthenticateWithLegacyApi (if the former fails to post the task) a
  // RepeatingCallback is needed. The callback will be executed at most once.
  auto barrier_callback = base::BarrierCallback<bool>(
      1, base::BindOnce(&VectorToFirstElement).Then(std::move(callback)));

  hr = base::win::PostAsyncHandlers(
      async_op.Get(),
      base::BindOnce(&OnAuthenticationReceived, barrier_callback, message),
      base::BindOnce(&OnAuthenticationAsyncOpFail, barrier_callback, message));

  if (FAILED(hr)) {
    RecordWindowsHelloAuthenticationResult(
        AuthenticationResultStatusWin::kFailedToPostTask);
    AuthenticateWithLegacyApi(message, barrier_callback);
    return;
  }
}

void PerformWin11Authentication(
    const std::u16string& message,
    base::OnceCallback<void(bool)> result_callback) {
  if (base::FeatureList::IsEnabled(
          password_manager::features::
              kAuthenticateUsingUserConsentVerifierInteropApi)) {
    PerformInteropWindowsHelloAuthenticationAsync(std::move(result_callback),
                                                  message);
    return;
  }
  AuthenticateWithLegacyApi(message, std::move(result_callback));
}

void PerformWin10Authentication(
    const std::u16string& message,
    base::OnceCallback<void(bool)> result_callback) {
  if (base::FeatureList::IsEnabled(
          password_manager::features::
              kAuthenticateUsingUserConsentVerifierApi)) {
    // Posting authentication using the new API on a background thread causes
    // Windows Hello dialog not to attach to Chrome's UI and instead it is
    // visible behind it. Running it on the default thread isn't that bad
    // because the thread itself is not blocked and there are operations
    // happening while the win hello dialog is visible.
    PerformWindowsHelloAuthenticationAsync(std::move(result_callback), message);
    return;
  }
  AuthenticateWithLegacyApi(message, std::move(result_callback));
}

}  // namespace

AuthenticatorWin::AuthenticatorWin() = default;

AuthenticatorWin::~AuthenticatorWin() = default;

void AuthenticatorWin::AuthenticateUser(
    const std::u16string& message,
    base::OnceCallback<void(bool)> result_callback) {
  RecordAuthenticationState(AuthenticationStateWin::kStarted);

  // TODO(b/349728186): Cleanup after Win11 solution is launched.
  if (base::win::GetVersion() >= base::win::Version::WIN11) {
    PerformWin11Authentication(
        message, std::move(result_callback)
                     .Then(base::BindOnce(RecordAuthenticationState,
                                          AuthenticationStateWin::kFinished)));
    return;
  }

  PerformWin10Authentication(
      message, std::move(result_callback)
                   .Then(base::BindOnce(RecordAuthenticationState,
                                        AuthenticationStateWin::kFinished)));
}

void AuthenticatorWin::CheckIfBiometricsAvailable(
    AvailabilityCallback callback) {
  scoped_refptr<base::SequencedTaskRunner> background_task_runner =
      base::ThreadPool::CreateCOMSTATaskRunner(
          {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
  DCHECK(background_task_runner);
  background_task_runner->PostTask(
      FROM_HERE,
      base::BindOnce(&GetBiometricAvailabilityFromWindows, std::move(callback),
                     base::SequencedTaskRunner::GetCurrentDefault()));
}

bool AuthenticatorWin::CanAuthenticateWithScreenLock() {
  return password_manager_util_win::CanAuthenticateWithScreenLock();
}