#include "device/fido/win/authenticator.h"
#include <windows.h>
#include <algorithm>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util_win.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "components/device_event_log/device_event_log.h"
#include "device/fido/authenticator_supported_options.h"
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/discoverable_credential_metadata.h"
#include "device/fido/features.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_request_handler_base.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/public_key_credential_descriptor.h"
#include "device/fido/win/type_conversions.h"
#include "device/fido/win/util.h"
#include "device/fido/win/webauthn_api.h"
#include "third_party/microsoft_webauthn/src/webauthn.h"
namespace device {
namespace {
WinWebAuthnApiAuthenticator::TestObserver* g_observer = nullptr;
AuthenticatorSupportedOptions WinWebAuthnApiOptions(int api_version) {
AuthenticatorSupportedOptions options;
options.is_platform_device =
AuthenticatorSupportedOptions::PlatformDevice::kBoth;
options.supports_resident_key = true;
options.user_verification_availability = AuthenticatorSupportedOptions::
UserVerificationAvailability::kSupportedAndConfigured;
options.supports_user_presence = true;
options.supports_cred_protect = api_version >= WEBAUTHN_API_VERSION_2;
options.enterprise_attestation = api_version >= WEBAUTHN_API_VERSION_3;
if (api_version >= WEBAUTHN_API_VERSION_3) {
options.large_blob_type = LargeBlobSupportType::kBespoke;
}
options.supports_min_pin_length_extension =
api_version >= WEBAUTHN_API_VERSION_3;
if (api_version >= WEBAUTHN_API_VERSION_3) {
options.max_cred_blob_length = 256;
}
options.supports_hmac_secret = true;
return options;
}
bool MayHaveWindowsHelloCredentials(
std::vector<PublicKeyCredentialDescriptor> allow_list) {
return allow_list.empty() ||
std::ranges::any_of(allow_list, [](const auto& credential) {
return credential.transports.empty() ||
base::Contains(credential.transports,
FidoTransportProtocol::kInternal);
});
}
void FilterFoundCredentials(
std::vector<DiscoverableCredentialMetadata>* found_creds,
const std::vector<PublicKeyCredentialDescriptor>& allow_list_creds) {
auto removed = std::ranges::remove_if(
*found_creds, [&allow_list_creds](const auto& found_cred) {
return std::ranges::none_of(
allow_list_creds, [&found_cred](const auto& allow_list_cred) {
return allow_list_cred.id == found_cred.cred_id;
});
});
found_creds->erase(removed.begin(), removed.end());
}
void SignalUnknownCredentialBlocking(WinWebAuthnApi* api,
const std::vector<uint8_t>& credential_id,
const std::string& relying_party_id) {
if (!base::FeatureList::IsEnabled(device::kWebAuthnHelloSignal) || !api ||
!api->IsAvailable() || !api->SupportsSilentDiscovery()) {
FIDO_LOG(DEBUG) << "SignalUnknownCredential unavailable for Windows Hello";
return;
}
std::vector<DiscoverableCredentialMetadata> credentials =
device::AuthenticatorEnumerateCredentialsBlocking(
api, base::UTF8ToUTF16(relying_party_id), false)
.second;
const auto credential_it = std::ranges::find_if(
credentials, [credential_id](const auto& credential) {
return credential.cred_id == credential_id;
});
if (credential_it == credentials.end()) {
FIDO_LOG(DEBUG) << "Unknown credential not found on Windows Hello";
return;
}
FIDO_LOG(DEBUG) << "Removing unknown Windows Hello credential";
api->DeletePlatformCredential(credential_id);
}
void SignalAllAcceptedCredentialsBlocking(
WinWebAuthnApi* api,
const std::string& relying_party_id,
const std::vector<uint8_t>& user_id,
const std::vector<std::vector<uint8_t>>& all_accepted_credential_ids) {
if (!base::FeatureList::IsEnabled(device::kWebAuthnHelloSignal) || !api ||
!api->IsAvailable() || !api->SupportsSilentDiscovery()) {
FIDO_LOG(DEBUG)
<< "SignalAllAcceptedCredentials unavailable for Windows Hello";
return;
}
FIDO_LOG(DEBUG)
<< "Removing Windows Hello credentials not on accepted credentials list";
std::vector<DiscoverableCredentialMetadata> credentials =
device::AuthenticatorEnumerateCredentialsBlocking(
api, base::UTF8ToUTF16(relying_party_id), false)
.second;
for (const DiscoverableCredentialMetadata& credential : credentials) {
if (credential.user.id != user_id ||
std::ranges::find(all_accepted_credential_ids, credential.cred_id) !=
all_accepted_credential_ids.end()) {
continue;
}
FIDO_LOG(DEBUG) << "Removing credential "
<< base::HexEncode(credential.cred_id);
api->DeletePlatformCredential(credential.cred_id);
}
}
}
void WinWebAuthnApiAuthenticator::SetGlobalObserverForTesting(
TestObserver* observer) {
CHECK(!observer || !g_observer);
g_observer = observer;
}
void WinWebAuthnApiAuthenticator::IsUserVerifyingPlatformAuthenticatorAvailable(
WinWebAuthnApi* api,
base::OnceCallback<void(bool is_available)> callback) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE, base::MayBlock(),
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(
[](WinWebAuthnApi* api) {
BOOL result;
if (!api || !api->IsAvailable()) {
return false;
}
return api->IsUserVerifyingPlatformAuthenticatorAvailable(
&result) == S_OK &&
result == TRUE;
},
api),
std::move(callback));
}
void WinWebAuthnApiAuthenticator::EnumeratePlatformCredentials(
WinWebAuthnApi* api,
base::OnceCallback<
void(std::vector<device::DiscoverableCredentialMetadata>)> callback) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE, base::MayBlock(),
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(AuthenticatorEnumerateCredentialsBlocking, api,
std::u16string_view(),
false),
base::BindOnce(
[](base::OnceCallback<void(
std::vector<device::DiscoverableCredentialMetadata>)> callback,
std::pair<bool, std::vector<DiscoverableCredentialMetadata>>
result) { std::move(callback).Run(std::move(result.second)); },
std::move(callback)));
}
void WinWebAuthnApiAuthenticator::DeletePlatformCredential(
WinWebAuthnApi* api,
base::span<const uint8_t> credential_id,
base::OnceCallback<void(bool)> callback) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE, base::MayBlock(),
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(
[](WinWebAuthnApi* api, std::vector<uint8_t> credential_id) -> bool {
return api && api->IsAvailable() &&
api->SupportsSilentDiscovery() &&
api->DeletePlatformCredential(credential_id) == S_OK;
},
api,
std::vector<uint8_t>(credential_id.begin(), credential_id.end())),
std::move(callback));
}
void WinWebAuthnApiAuthenticator::SignalUnknownCredential(
WinWebAuthnApi* api,
const std::vector<uint8_t>& credential_id,
const std::string& relying_party_id) {
base::OnceClosure reply = base::DoNothing();
if (g_observer) {
reply = base::BindOnce(&TestObserver::OnSignalUnknownCredential,
base::Unretained(g_observer));
}
base::ThreadPool::PostTaskAndReply(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE, base::MayBlock(),
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(SignalUnknownCredentialBlocking, api, credential_id,
relying_party_id),
std::move(reply));
}
void WinWebAuthnApiAuthenticator::SignalAllAcceptedCredentials(
WinWebAuthnApi* api,
const std::string& relying_party_id,
const std::vector<uint8_t>& user_id,
const std::vector<std::vector<uint8_t>>& all_accepted_credential_ids) {
base::OnceClosure reply = base::DoNothing();
if (g_observer) {
reply = base::BindOnce(&TestObserver::OnSignalAllAcceptedCredentials,
base::Unretained(g_observer));
}
base::ThreadPool::PostTaskAndReply(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE, base::MayBlock(),
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(SignalAllAcceptedCredentialsBlocking, api,
relying_party_id, user_id, all_accepted_credential_ids),
std::move(reply));
}
WinWebAuthnApiAuthenticator::WinWebAuthnApiAuthenticator(
HWND current_window,
WinWebAuthnApi* win_api)
: options_(WinWebAuthnApiOptions(win_api->Version())),
current_window_(current_window),
win_api_(win_api) {
CHECK(win_api_->IsAvailable());
CoCreateGuid(&cancellation_id_);
}
WinWebAuthnApiAuthenticator::~WinWebAuthnApiAuthenticator() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Cancel();
}
void WinWebAuthnApiAuthenticator::InitializeAuthenticator(
base::OnceClosure callback) {
std::move(callback).Run();
}
void WinWebAuthnApiAuthenticator::MakeCredential(
CtapMakeCredentialRequest request,
MakeCredentialOptions options,
MakeCredentialCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (is_pending_) {
NOTREACHED();
}
is_pending_ = true;
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::TaskPriority::USER_BLOCKING, base::MayBlock()},
base::BindOnce(&AuthenticatorMakeCredentialBlocking, win_api_,
current_window_, cancellation_id_, std::move(request),
std::move(options)),
base::BindOnce(&WinWebAuthnApiAuthenticator::MakeCredentialDone,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void WinWebAuthnApiAuthenticator::MakeCredentialDone(
MakeCredentialCallback callback,
std::pair<MakeCredentialStatus,
std::optional<AuthenticatorMakeCredentialResponse>> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(is_pending_);
is_pending_ = false;
if (waiting_for_cancellation_) {
waiting_for_cancellation_ = false;
return;
}
if (result.first != MakeCredentialStatus::kSuccess) {
std::move(callback).Run(result.first, std::nullopt);
return;
}
CHECK(result.second);
std::move(callback).Run(result.first, std::move(result.second));
}
void WinWebAuthnApiAuthenticator::GetAssertion(CtapGetAssertionRequest request,
CtapGetAssertionOptions options,
GetAssertionCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!is_pending_);
if (is_pending_) {
return;
}
is_pending_ = true;
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::TaskPriority::USER_BLOCKING, base::MayBlock()},
base::BindOnce(&AuthenticatorGetAssertionBlocking, win_api_,
current_window_, cancellation_id_, std::move(request),
std::move(options)),
base::BindOnce(&WinWebAuthnApiAuthenticator::GetAssertionDone,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void WinWebAuthnApiAuthenticator::GetAssertionDone(
GetAssertionCallback callback,
std::pair<GetAssertionStatus,
std::optional<AuthenticatorGetAssertionResponse>> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(is_pending_);
is_pending_ = false;
if (waiting_for_cancellation_) {
waiting_for_cancellation_ = false;
return;
}
if (result.first != GetAssertionStatus::kSuccess) {
std::move(callback).Run(result.first, {});
return;
}
CHECK(result.second);
std::vector<AuthenticatorGetAssertionResponse> responses;
responses.emplace_back(std::move(*result.second));
std::move(callback).Run(result.first, std::move(responses));
}
void WinWebAuthnApiAuthenticator::GetPlatformCredentialInfoForRequest(
const CtapGetAssertionRequest& request,
const CtapGetAssertionOptions& request_options,
GetPlatformCredentialInfoForRequestCallback callback) {
if (!MayHaveWindowsHelloCredentials(request.allow_list)) {
std::move(callback).Run(
{},
FidoRequestHandlerBase::RecognizedCredential::kNoRecognizedCredential);
return;
}
FIDO_LOG(DEBUG) << "Silently discovering credentials for " << request.rp_id;
auto [success, credentials] = AuthenticatorEnumerateCredentialsBlocking(
win_api_, base::UTF8ToUTF16(request.rp_id),
request_options.is_off_the_record_context);
if (!success) {
std::move(callback).Run(
{},
FidoRequestHandlerBase::RecognizedCredential::kUnknown);
return;
}
if (base::FeatureList::IsEnabled(kWebAuthenticationFixWindowsHelloRdp) &&
credentials.empty() && fido::win::IsRemoteDesktopSession()) {
FIDO_LOG(DEBUG) << "RDP detected and no credentials returned. Assuming "
"Windows WebAuthn enumeration doesn't work.";
std::move(callback).Run(
{},
FidoRequestHandlerBase::RecognizedCredential::kUnknown);
return;
}
if (!request.allow_list.empty()) {
FilterFoundCredentials(&credentials, request.allow_list);
}
auto recognized = credentials.empty()
? FidoRequestHandlerBase::RecognizedCredential::
kNoRecognizedCredential
: FidoRequestHandlerBase::RecognizedCredential::
kHasRecognizedCredential;
std::move(callback).Run(std::move(credentials), recognized);
}
void WinWebAuthnApiAuthenticator::GetTouch(base::OnceClosure callback) {
NOTREACHED();
}
void WinWebAuthnApiAuthenticator::Cancel() {
if (!is_pending_ || waiting_for_cancellation_) {
return;
}
waiting_for_cancellation_ = true;
win_api_->CancelCurrentOperation(&cancellation_id_);
}
AuthenticatorType WinWebAuthnApiAuthenticator::GetType() const {
return AuthenticatorType::kWinNative;
}
std::string WinWebAuthnApiAuthenticator::GetId() const {
return "WinWebAuthnApiAuthenticator";
}
std::optional<FidoTransportProtocol>
WinWebAuthnApiAuthenticator::AuthenticatorTransport() const {
return std::nullopt;
}
const AuthenticatorSupportedOptions& WinWebAuthnApiAuthenticator::Options()
const {
return options_;
}
base::WeakPtr<FidoAuthenticator> WinWebAuthnApiAuthenticator::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
bool WinWebAuthnApiAuthenticator::ShowsResidentCredentialNotice() const {
return win_api_->Version() >= WEBAUTHN_API_VERSION_2;
}
}