[webauthn] Handle Windows Hello not enabled better
The Windows WebAuthn API will immediately return an error for internal only requests if Windows Hello is not enabled. This is undesirable as it is not clear to users why their sign in attempt failed, and because it leaks to the RP that the feature is disabled. Different reasons return different errors, sometimes indistinguishable from the feature being available with no credentials. This CL updates Chrome to avoid dispatching to the Windows API for requests that are internal only if IsUVPAA returns false, and instead show a helpful error to the user. Strings have been approved by UXW. Fixed: 1412872 Change-Id: I51c2a7f9b0a583745112260a31c98faf710b5cf7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4295254 Reviewed-by: Martin Kreichgauer <martinkr@google.com> Commit-Queue: Nina Satragno <nsatragno@chromium.org> Auto-Submit: Nina Satragno <nsatragno@chromium.org> Cr-Commit-Position: refs/heads/main@{#1111967}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
73988309d0
commit
c9a35cc3ef
@ -13388,6 +13388,12 @@ Please help our engineers fix this problem. Tell us what happened right before y
|
||||
<message name="IDS_WEBAUTHN_ERROR_INTERNAL_UNRECOGNIZED_DESCRIPTION" desc="Description in the dialog shown when the user's could not sign in to a web site using a hardware-based authentication mechanism built in to the computer (e.g. a fingerprint reader), because they never registered the device with this website before.">
|
||||
Your identity couldn't be verified
|
||||
</message>
|
||||
<message name="IDS_WEBAUTHN_WINDOWS_HELLO_NOT_ENABLED_TITLE" desc="Title of the dialog shown when a website requires Windows Hello to sign in with a passkey, but Windows Hello is not enabled for the user's device.">
|
||||
Turn on Windows Hello
|
||||
</message>
|
||||
<message name="IDS_WEBAUTHN_WINDOWS_HELLO_NOT_ENABLED_DESCRIPTION" desc="Description of the dialog shown when a website requires Windows Hello to sign in with a passkey, but Windows Hello is not enabled for the user's device.">
|
||||
To sign into this site with a passkey, you need to turn on Windows Hello in settings. Then return to this site and try again.
|
||||
</message>
|
||||
<message name="IDS_WEBAUTHN_ERROR_NO_TRANSPORTS_TITLE" desc="Title of the dialog shown when the user could not sign in to a web site, because their computer did not support any of the hardware-based authentication mechanisms desired by the web site.">
|
||||
Your identity couldn't be verified
|
||||
</message>
|
||||
|
1
chrome/app/generated_resources_grd/IDS_WEBAUTHN_WINDOWS_HELLO_NOT_ENABLED_DESCRIPTION.png.sha1
Normal file
1
chrome/app/generated_resources_grd/IDS_WEBAUTHN_WINDOWS_HELLO_NOT_ENABLED_DESCRIPTION.png.sha1
Normal file
@ -0,0 +1 @@
|
||||
4f9d4e26e9ed1ce628b861ca2854503b65947590
|
1
chrome/app/generated_resources_grd/IDS_WEBAUTHN_WINDOWS_HELLO_NOT_ENABLED_TITLE.png.sha1
Normal file
1
chrome/app/generated_resources_grd/IDS_WEBAUTHN_WINDOWS_HELLO_NOT_ENABLED_TITLE.png.sha1
Normal file
@ -0,0 +1 @@
|
||||
4f9d4e26e9ed1ce628b861ca2854503b65947590
|
@ -165,6 +165,11 @@ std::unique_ptr<AuthenticatorRequestSheetView> CreateSheetViewForCurrentStepOf(
|
||||
std::make_unique<AuthenticatorInternalUnrecognizedErrorSheetModel>(
|
||||
dialog_model));
|
||||
break;
|
||||
case Step::kErrorWindowsHelloNotEnabled:
|
||||
sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
|
||||
AuthenticatorGenericErrorSheetModel::ForWindowsHelloNotEnabled(
|
||||
dialog_model));
|
||||
break;
|
||||
case Step::kBlePowerOnAutomatic:
|
||||
sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
|
||||
std::make_unique<AuthenticatorBlePowerOnAutomaticSheetModel>(
|
||||
|
@ -28,7 +28,7 @@
|
||||
// Run with:
|
||||
//
|
||||
// --gtest_filter=BrowserUiTest.Invoke --test-launcher-interactive \
|
||||
// --ui=All/AuthenticatorDialogTest.InvokeUi_${test_name}
|
||||
// --ui=AuthenticatorDialogTest.InvokeUi_${test_name}
|
||||
//
|
||||
// where test_name is the second arg to IN_PROC_BROWSER_TEST_F().
|
||||
|
||||
@ -98,6 +98,9 @@ class AuthenticatorDialogTest : public DialogBrowserTest {
|
||||
} else if (name == "key_already_registered") {
|
||||
model_->SetCurrentStepForTesting(
|
||||
AuthenticatorRequestDialogModel::Step::kKeyAlreadyRegistered);
|
||||
} else if (name == "windows_hello_not_enabled") {
|
||||
model_->SetCurrentStepForTesting(
|
||||
AuthenticatorRequestDialogModel::Step::kErrorWindowsHelloNotEnabled);
|
||||
} else if (name == "internal_unrecognized_error") {
|
||||
model_->SetCurrentStepForTesting(
|
||||
AuthenticatorRequestDialogModel::Step::kErrorInternalUnrecognized);
|
||||
@ -311,7 +314,7 @@ class AuthenticatorDialogTest : public DialogBrowserTest {
|
||||
#endif
|
||||
|
||||
model_->StartFlow(std::move(transport_availability),
|
||||
/*use_location_bar_bubble=*/false,
|
||||
/*is_conditional_mediation=*/false,
|
||||
/*prefer_native_api=*/false);
|
||||
}
|
||||
|
||||
@ -360,6 +363,11 @@ IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
|
||||
ShowAndVerifyUi();
|
||||
}
|
||||
|
||||
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
|
||||
InvokeUi_windows_hello_not_enabled) {
|
||||
ShowAndVerifyUi();
|
||||
}
|
||||
|
||||
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
|
||||
InvokeUi_internal_unrecognized_error) {
|
||||
ShowAndVerifyUi();
|
||||
|
@ -1037,6 +1037,16 @@ AuthenticatorGenericErrorSheetModel::ForStorageFull(
|
||||
l10n_util::GetStringUTF16(IDS_WEBAUTHN_STORAGE_FULL_DESC)));
|
||||
}
|
||||
|
||||
std::unique_ptr<AuthenticatorGenericErrorSheetModel>
|
||||
AuthenticatorGenericErrorSheetModel::ForWindowsHelloNotEnabled(
|
||||
AuthenticatorRequestDialogModel* dialog_model) {
|
||||
return base::WrapUnique(new AuthenticatorGenericErrorSheetModel(
|
||||
dialog_model,
|
||||
l10n_util::GetStringUTF16(IDS_WEBAUTHN_WINDOWS_HELLO_NOT_ENABLED_TITLE),
|
||||
l10n_util::GetStringUTF16(
|
||||
IDS_WEBAUTHN_WINDOWS_HELLO_NOT_ENABLED_DESCRIPTION)));
|
||||
}
|
||||
|
||||
AuthenticatorGenericErrorSheetModel::AuthenticatorGenericErrorSheetModel(
|
||||
AuthenticatorRequestDialogModel* dialog_model,
|
||||
std::u16string title,
|
||||
|
@ -437,6 +437,8 @@ class AuthenticatorGenericErrorSheetModel : public AuthenticatorSheetModelBase {
|
||||
ForMissingCapability(AuthenticatorRequestDialogModel* dialog_model);
|
||||
static std::unique_ptr<AuthenticatorGenericErrorSheetModel> ForStorageFull(
|
||||
AuthenticatorRequestDialogModel* dialog_model);
|
||||
static std::unique_ptr<AuthenticatorGenericErrorSheetModel>
|
||||
ForWindowsHelloNotEnabled(AuthenticatorRequestDialogModel* dialog_model);
|
||||
|
||||
private:
|
||||
AuthenticatorGenericErrorSheetModel(
|
||||
|
@ -947,6 +947,13 @@ void AuthenticatorRequestDialogModel::StartWinNativeApi(
|
||||
DCHECK(transport_availability_.has_win_native_api_authenticator);
|
||||
current_mechanism_ = mechanism_index;
|
||||
|
||||
if (transport_availability_.request_is_internal_only &&
|
||||
!transport_availability_.win_is_uvpaa) {
|
||||
offer_try_again_in_ui_ = false;
|
||||
SetCurrentStep(Step::kErrorWindowsHelloNotEnabled);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resident_key_requirement() !=
|
||||
device::ResidentKeyRequirement::kDiscouraged &&
|
||||
!transport_availability_.win_native_ui_shows_resident_credential_notice) {
|
||||
|
@ -75,6 +75,7 @@ class AuthenticatorRequestDialogModel {
|
||||
kErrorNoAvailableTransports,
|
||||
kErrorNoPasskeys,
|
||||
kErrorInternalUnrecognized,
|
||||
kErrorWindowsHelloNotEnabled,
|
||||
|
||||
// The request is already complete, but the error dialog should wait
|
||||
// until user acknowledgement.
|
||||
@ -263,6 +264,7 @@ class AuthenticatorRequestDialogModel {
|
||||
current_step() == Step::kKeyNotRegistered ||
|
||||
current_step() == Step::kKeyAlreadyRegistered ||
|
||||
current_step() == Step::kMissingCapability ||
|
||||
current_step() == Step::kErrorWindowsHelloNotEnabled ||
|
||||
current_step() == Step::kClosed;
|
||||
}
|
||||
|
||||
|
@ -624,6 +624,21 @@ TEST_F(AuthenticatorRequestDialogModelTest, WinCancel) {
|
||||
EXPECT_FALSE(model.OnWinUserCancelled());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AuthenticatorRequestDialogModelTest, WinNoPlatformAuthenticator) {
|
||||
AuthenticatorRequestDialogModel::TransportAvailabilityInfo tai;
|
||||
tai.request_type = device::FidoRequestType::kMakeCredential;
|
||||
tai.request_is_internal_only = true;
|
||||
tai.win_is_uvpaa = false;
|
||||
tai.has_win_native_api_authenticator = true;
|
||||
AuthenticatorRequestDialogModel model(/*render_frame_host=*/nullptr);
|
||||
model.StartFlow(std::move(tai), /*is_conditional_mediation=*/false,
|
||||
/*prefer_native_api=*/false);
|
||||
EXPECT_EQ(
|
||||
model.current_step(),
|
||||
AuthenticatorRequestDialogModel::Step::kErrorWindowsHelloNotEnabled);
|
||||
EXPECT_FALSE(model.offer_try_again_in_ui());
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(AuthenticatorRequestDialogModelTest, NoAvailableTransports) {
|
||||
|
@ -1262,7 +1262,6 @@ void AuthenticatorCommonImpl::OnRegisterResponse(
|
||||
nullptr, Focus::kDoCheck);
|
||||
} else {
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kKeyAlreadyRegistered,
|
||||
blink::mojom::AuthenticatorStatus::CREDENTIAL_EXCLUDED);
|
||||
@ -1276,70 +1275,60 @@ void AuthenticatorCommonImpl::OnRegisterResponse(
|
||||
return;
|
||||
case device::MakeCredentialStatus::kUserConsentDenied:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kUserConsentDenied,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::MakeCredentialStatus::kSoftPINBlock:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kSoftPINBlock,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::MakeCredentialStatus::kHardPINBlock:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kHardPINBlock,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::MakeCredentialStatus::kAuthenticatorRemovedDuringPINEntry:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kAuthenticatorRemovedDuringPINEntry,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::MakeCredentialStatus::kAuthenticatorMissingResidentKeys:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kAuthenticatorMissingResidentKeys,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::MakeCredentialStatus::kAuthenticatorMissingUserVerification:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kAuthenticatorMissingUserVerification,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::MakeCredentialStatus::kAuthenticatorMissingLargeBlob:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kAuthenticatorMissingLargeBlob,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::MakeCredentialStatus::kNoCommonAlgorithms:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kNoCommonAlgorithms,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::MakeCredentialStatus::kStorageFull:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kStorageFull,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::MakeCredentialStatus::kWinNotAllowedError:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kWinUserCancelled,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
@ -1510,7 +1499,6 @@ void AuthenticatorCommonImpl::OnSignResponse(
|
||||
switch (status_code) {
|
||||
case device::GetAssertionStatus::kUserConsentButCredentialNotRecognized:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kKeyNotRegistered,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
@ -1522,49 +1510,42 @@ void AuthenticatorCommonImpl::OnSignResponse(
|
||||
return;
|
||||
case device::GetAssertionStatus::kUserConsentDenied:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kUserConsentDenied,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::GetAssertionStatus::kSoftPINBlock:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kSoftPINBlock,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::GetAssertionStatus::kHardPINBlock:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kHardPINBlock,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::GetAssertionStatus::kAuthenticatorRemovedDuringPINEntry:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kAuthenticatorRemovedDuringPINEntry,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::GetAssertionStatus::kAuthenticatorMissingResidentKeys:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kAuthenticatorMissingResidentKeys,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::GetAssertionStatus::kAuthenticatorMissingUserVerification:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kAuthenticatorMissingUserVerification,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
return;
|
||||
case device::GetAssertionStatus::kWinNotAllowedError:
|
||||
SignalFailureToRequestDelegate(
|
||||
authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::
|
||||
kWinUserCancelled,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
@ -1626,7 +1607,6 @@ void AuthenticatorCommonImpl::OnAccountSelected(
|
||||
}
|
||||
|
||||
void AuthenticatorCommonImpl::SignalFailureToRequestDelegate(
|
||||
const ::device::FidoAuthenticator* authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason reason,
|
||||
blink::mojom::AuthenticatorStatus status) {
|
||||
error_awaiting_user_acknowledgement_ = status;
|
||||
@ -1664,7 +1644,6 @@ void AuthenticatorCommonImpl::OnTimeout() {
|
||||
|
||||
DCHECK(request_delegate_);
|
||||
SignalFailureToRequestDelegate(
|
||||
/*authenticator=*/nullptr,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason::kTimeout,
|
||||
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
|
||||
}
|
||||
|
@ -173,7 +173,6 @@ class CONTENT_EXPORT AuthenticatorCommonImpl : public AuthenticatorCommon {
|
||||
// The request delegate decides whether to present the user with a visual
|
||||
// error before the request is finally resolved with |status|.
|
||||
void SignalFailureToRequestDelegate(
|
||||
const device::FidoAuthenticator* authenticator,
|
||||
AuthenticatorRequestClientDelegate::InterestingFailureReason reason,
|
||||
blink::mojom::AuthenticatorStatus status);
|
||||
|
||||
|
@ -51,13 +51,18 @@ struct TransportAvailabilityCallbackReadiness {
|
||||
// request.
|
||||
bool platform_credential_check_pending = false;
|
||||
|
||||
// win_is_uvpaa_check_pending is true if |OnTransportAvailabilityEnumerated|
|
||||
// callback is pending |OnIsUvpaa| being called.
|
||||
bool win_is_uvpaa_check_pending = false;
|
||||
|
||||
// num_discoveries_pending is the number of discoveries that are still yet to
|
||||
// signal that they have started.
|
||||
unsigned num_discoveries_pending = 0;
|
||||
|
||||
bool CanMakeCallback() const {
|
||||
return !callback_made && !ble_information_pending &&
|
||||
!platform_credential_check_pending && num_discoveries_pending == 0;
|
||||
!platform_credential_check_pending && !win_is_uvpaa_check_pending &&
|
||||
num_discoveries_pending == 0;
|
||||
}
|
||||
};
|
||||
|
||||
@ -121,16 +126,20 @@ void FidoRequestHandlerBase::InitDiscoveries(
|
||||
// The Windows WebAuthn API is available. On this platform, communicating
|
||||
// with authenticator devices directly is blocked by the OS, so we need to
|
||||
// go through the native API instead. No device discoveries may be
|
||||
// instantiated.
|
||||
// instantiated. The embedder will be responsible for dispatch of the
|
||||
// authenticator and whether they display any UI in addition to the one
|
||||
// provided by the OS.
|
||||
win_discovery->set_observer(this);
|
||||
discoveries_.push_back(std::move(win_discovery));
|
||||
|
||||
// Setting |has_win_native_api_authenticator| ensures
|
||||
// NotifyObserverTransportAvailability() will not be invoked before
|
||||
// Windows Authenticator has been added. The embedder will be
|
||||
// responsible for dispatch of the authenticator and whether they
|
||||
// display any UI in addition to the one provided by the OS.
|
||||
transport_availability_info_.has_win_native_api_authenticator = true;
|
||||
transport_availability_callback_readiness_->win_is_uvpaa_check_pending =
|
||||
true;
|
||||
WinWebAuthnApiAuthenticator::IsUserVerifyingPlatformAuthenticatorAvailable(
|
||||
transport_availability_info_.is_off_the_record_context,
|
||||
fido_discovery_factory->win_webauthn_api(),
|
||||
base::BindOnce(&FidoRequestHandlerBase::OnWinIsUvpaa,
|
||||
weak_factory_.GetWeakPtr()));
|
||||
|
||||
// Allow caBLE as a potential additional transport if requested by
|
||||
// the implementing class because it is not subject to the OS'
|
||||
@ -445,6 +454,13 @@ void FidoRequestHandlerBase::ConstructBleAdapterPowerManager() {
|
||||
bluetooth_adapter_manager_ = std::make_unique<BleAdapterManager>(this);
|
||||
}
|
||||
|
||||
void FidoRequestHandlerBase::OnWinIsUvpaa(bool is_uvpaa) {
|
||||
transport_availability_info_.win_is_uvpaa = is_uvpaa;
|
||||
transport_availability_callback_readiness_->win_is_uvpaa_check_pending =
|
||||
false;
|
||||
MaybeSignalTransportsEnumerated();
|
||||
}
|
||||
|
||||
void FidoRequestHandlerBase::StopDiscoveries() {
|
||||
for (const auto& discovery : discoveries_) {
|
||||
discovery->Stop();
|
||||
|
@ -109,6 +109,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase
|
||||
// when creating a resident credential.
|
||||
bool win_native_ui_shows_resident_credential_notice = false;
|
||||
|
||||
// Whether the native Windows API reports that a user verifying platform
|
||||
// authenticator is available.
|
||||
bool win_is_uvpaa = false;
|
||||
|
||||
// Contains the authenticator ID of the native Windows
|
||||
// authenticator if |has_win_native_api_authenticator| is true.
|
||||
// This allows the observer to distinguish it from other
|
||||
@ -139,6 +143,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase
|
||||
// contained credentials that could have been on the local device but
|
||||
// weren't.
|
||||
bool transport_list_did_include_internal = false;
|
||||
|
||||
// request_is_internal_only indicates that this request can only be serviced
|
||||
// by internal authenticators (e.g. due to the attachment setting).
|
||||
bool request_is_internal_only = false;
|
||||
};
|
||||
|
||||
class COMPONENT_EXPORT(DEVICE_FIDO) Observer {
|
||||
@ -348,6 +356,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase
|
||||
void InitializeAuthenticatorAndDispatchRequest(
|
||||
const std::string& authenticator_id);
|
||||
void ConstructBleAdapterPowerManager();
|
||||
void OnWinIsUvpaa(bool is_uvpaa);
|
||||
|
||||
AuthenticatorMap active_authenticators_;
|
||||
std::vector<std::unique_ptr<FidoDiscoveryBase>> discoveries_;
|
||||
|
@ -623,12 +623,27 @@ TEST_F(FidoRequestHandlerTest,
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
|
||||
TEST_F(FidoRequestHandlerTest, TransportAvailabilityOfWindowsAuthenticator) {
|
||||
static const struct {
|
||||
bool api_available = false;
|
||||
bool is_uvpaa = false;
|
||||
} kTestCases[] = {
|
||||
/* clang-format off */
|
||||
/* api_available is_uvpaa */
|
||||
{true, true},
|
||||
{true, false},
|
||||
{false, false},
|
||||
/* clang-format on */
|
||||
};
|
||||
FakeWinWebAuthnApi api;
|
||||
fake_discovery_factory_.set_win_webauthn_api(&api);
|
||||
for (const bool api_available : {false, true}) {
|
||||
SCOPED_TRACE(::testing::Message() << "api_available=" << api_available);
|
||||
api.set_available(api_available);
|
||||
for (const auto& test_case : kTestCases) {
|
||||
SCOPED_TRACE(::testing::Message()
|
||||
<< "api_available=" << test_case.api_available);
|
||||
SCOPED_TRACE(::testing::Message() << "is_uvpaa=" << test_case.is_uvpaa);
|
||||
api.set_available(test_case.api_available);
|
||||
api.set_is_uvpaa(test_case.is_uvpaa);
|
||||
|
||||
TestObserver observer;
|
||||
ForgeNextHidDiscovery();
|
||||
@ -639,19 +654,19 @@ TEST_F(FidoRequestHandlerTest, TransportAvailabilityOfWindowsAuthenticator) {
|
||||
|
||||
// If the windows API is not enabled, the request is dispatched to the USB
|
||||
// discovery. Simulate a success to fill the transport availability info.
|
||||
if (!api_available)
|
||||
if (!test_case.api_available) {
|
||||
discovery()->WaitForCallToStartAndSimulateSuccess();
|
||||
|
||||
task_environment_.FastForwardUntilNoTasksRemain();
|
||||
}
|
||||
|
||||
auto transport_availability_info =
|
||||
observer.WaitForTransportAvailabilityInfo();
|
||||
EXPECT_EQ(transport_availability_info.available_transports.empty(),
|
||||
api_available);
|
||||
test_case.api_available);
|
||||
EXPECT_EQ(transport_availability_info.has_win_native_api_authenticator,
|
||||
api_available);
|
||||
test_case.api_available);
|
||||
EXPECT_EQ(transport_availability_info.win_native_api_authenticator_id,
|
||||
api_available ? "WinWebAuthnApiAuthenticator" : "");
|
||||
test_case.api_available ? "WinWebAuthnApiAuthenticator" : "");
|
||||
EXPECT_EQ(transport_availability_info.win_is_uvpaa, test_case.is_uvpaa);
|
||||
}
|
||||
}
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
@ -230,6 +230,8 @@ TEST_F(FidoGetAssertionHandlerTest, TransportAvailabilityInfo) {
|
||||
request_handler->transport_availability_info().has_empty_allow_list);
|
||||
EXPECT_FALSE(request_handler->transport_availability_info()
|
||||
.is_only_hybrid_or_internal);
|
||||
EXPECT_FALSE(request_handler->transport_availability_info()
|
||||
.request_is_internal_only);
|
||||
}
|
||||
{
|
||||
// Internal and a phone.
|
||||
@ -244,6 +246,8 @@ TEST_F(FidoGetAssertionHandlerTest, TransportAvailabilityInfo) {
|
||||
request_handler->transport_availability_info().has_empty_allow_list);
|
||||
EXPECT_TRUE(request_handler->transport_availability_info()
|
||||
.is_only_hybrid_or_internal);
|
||||
EXPECT_FALSE(request_handler->transport_availability_info()
|
||||
.request_is_internal_only);
|
||||
}
|
||||
{
|
||||
// Internal, a phone, and USB.
|
||||
@ -259,6 +263,8 @@ TEST_F(FidoGetAssertionHandlerTest, TransportAvailabilityInfo) {
|
||||
request_handler->transport_availability_info().has_empty_allow_list);
|
||||
EXPECT_FALSE(request_handler->transport_availability_info()
|
||||
.is_only_hybrid_or_internal);
|
||||
EXPECT_FALSE(request_handler->transport_availability_info()
|
||||
.request_is_internal_only);
|
||||
}
|
||||
{
|
||||
// Only USB.
|
||||
@ -272,6 +278,8 @@ TEST_F(FidoGetAssertionHandlerTest, TransportAvailabilityInfo) {
|
||||
request_handler->transport_availability_info().has_empty_allow_list);
|
||||
EXPECT_FALSE(request_handler->transport_availability_info()
|
||||
.is_only_hybrid_or_internal);
|
||||
EXPECT_FALSE(request_handler->transport_availability_info()
|
||||
.request_is_internal_only);
|
||||
}
|
||||
{
|
||||
// A phone and an unknown (empty) transport credential.
|
||||
@ -285,6 +293,24 @@ TEST_F(FidoGetAssertionHandlerTest, TransportAvailabilityInfo) {
|
||||
request_handler->transport_availability_info().has_empty_allow_list);
|
||||
EXPECT_FALSE(request_handler->transport_availability_info()
|
||||
.is_only_hybrid_or_internal);
|
||||
EXPECT_FALSE(request_handler->transport_availability_info()
|
||||
.request_is_internal_only);
|
||||
}
|
||||
{
|
||||
// Internal only.
|
||||
auto request_handler = CreateGetAssertionHandlerWithRequestedTransports(
|
||||
{{FidoTransportProtocol::kInternal},
|
||||
{FidoTransportProtocol::kInternal}});
|
||||
EXPECT_EQ(FidoRequestType::kGetAssertion,
|
||||
request_handler->transport_availability_info().request_type);
|
||||
EXPECT_TRUE(request_handler->transport_availability_info()
|
||||
.transport_list_did_include_internal);
|
||||
EXPECT_FALSE(
|
||||
request_handler->transport_availability_info().has_empty_allow_list);
|
||||
EXPECT_TRUE(request_handler->transport_availability_info()
|
||||
.is_only_hybrid_or_internal);
|
||||
EXPECT_TRUE(request_handler->transport_availability_info()
|
||||
.request_is_internal_only);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,6 +375,13 @@ GetAssertionRequestHandler::GetAssertionRequestHandler(
|
||||
base::Contains(cred.transports,
|
||||
FidoTransportProtocol::kInternal);
|
||||
});
|
||||
transport_availability_info().request_is_internal_only =
|
||||
!request_.allow_list.empty() &&
|
||||
base::ranges::all_of(
|
||||
request_.allow_list, [](const PublicKeyCredentialDescriptor& cred) {
|
||||
return cred.transports ==
|
||||
std::vector{FidoTransportProtocol::kInternal};
|
||||
});
|
||||
|
||||
FIDO_LOG(EVENT) << "Starting GetAssertion flow";
|
||||
Start();
|
||||
|
@ -181,6 +181,35 @@ TEST_F(FidoMakeCredentialHandlerTest, TransportAvailabilityInfoRk) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(FidoMakeCredentialHandlerTest, TransportAvailabilityInfoIsInternalOnly) {
|
||||
{
|
||||
auto request_handler =
|
||||
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
|
||||
AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
|
||||
UserVerificationRequirement::kPreferred));
|
||||
EXPECT_FALSE(request_handler->transport_availability_info()
|
||||
.request_is_internal_only);
|
||||
}
|
||||
{
|
||||
auto request_handler =
|
||||
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
|
||||
AuthenticatorAttachment::kCrossPlatform,
|
||||
ResidentKeyRequirement::kDiscouraged,
|
||||
UserVerificationRequirement::kPreferred));
|
||||
EXPECT_FALSE(request_handler->transport_availability_info()
|
||||
.request_is_internal_only);
|
||||
}
|
||||
{
|
||||
auto request_handler =
|
||||
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
|
||||
AuthenticatorAttachment::kPlatform,
|
||||
ResidentKeyRequirement::kDiscouraged,
|
||||
UserVerificationRequirement::kPreferred));
|
||||
EXPECT_TRUE(request_handler->transport_availability_info()
|
||||
.request_is_internal_only);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(FidoMakeCredentialHandlerTest, TestCtap2MakeCredential) {
|
||||
auto request_handler = CreateMakeCredentialHandler();
|
||||
discovery()->WaitForCallToStartAndSimulateSuccess();
|
||||
|
@ -416,6 +416,8 @@ MakeCredentialRequestHandler::MakeCredentialRequestHandler(
|
||||
options_.is_off_the_record_context;
|
||||
transport_availability_info().resident_key_requirement =
|
||||
options_.resident_key;
|
||||
transport_availability_info().request_is_internal_only =
|
||||
options_.authenticator_attachment == AuthenticatorAttachment::kPlatform;
|
||||
|
||||
base::flat_set<FidoTransportProtocol> allowed_transports =
|
||||
GetTransportsAllowedByRP(options.authenticator_attachment);
|
||||
|
Reference in New Issue
Block a user