| // Copyright 2018 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/webauthn/chrome_authenticator_request_delegate.h" |
| |
| #include <array> |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/check_op.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/span.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/i18n/time_formatting.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/strings/string_util.h" |
| #include "base/time/default_tick_clock.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/extensions/api/web_authentication_proxy/web_authentication_proxy_service.h" |
| #include "chrome/browser/net/system_network_context_manager.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_observer.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "chrome/browser/sync/device_info_sync_service_factory.h" |
| #include "chrome/browser/sync/sync_service_factory.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_navigator.h" |
| #include "chrome/browser/ui/browser_navigator_params.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/passwords/passwords_client_ui_delegate.h" |
| #include "chrome/browser/ui/webauthn/passkey_upgrade_request_controller.h" |
| #include "chrome/browser/ui/webauthn/user_actions.h" |
| #include "chrome/browser/webauthn/authenticator_request_dialog_controller.h" |
| #include "chrome/browser/webauthn/authenticator_request_dialog_model.h" |
| #include "chrome/browser/webauthn/cablev2_devices.h" |
| #include "chrome/browser/webauthn/enclave_manager.h" |
| #include "chrome/browser/webauthn/gpm_enclave_controller.h" |
| #include "chrome/browser/webauthn/passkey_model_factory.h" |
| #include "chrome/browser/webauthn/webauthn_metrics_util.h" |
| #include "chrome/browser/webauthn/webauthn_pref_names.h" |
| #include "chrome/common/chrome_version.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/device_event_log/device_event_log.h" |
| #include "components/password_manager/core/common/password_manager_pref_names.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/signin/public/base/consent_level.h" |
| #include "components/signin/public/identity_manager/identity_manager.h" |
| #include "components/sync/protocol/webauthn_credential_specifics.pb.h" |
| #include "components/sync/service/sync_service.h" |
| #include "components/sync_device_info/device_info.h" |
| #include "components/sync_device_info/device_info_sync_service.h" |
| #include "components/sync_device_info/device_info_tracker.h" |
| #include "components/trusted_vault/frontend_trusted_vault_connection.h" |
| #include "components/user_prefs/user_prefs.h" |
| #include "components/webauthn/core/browser/passkey_model.h" |
| #include "content/public/browser/authenticator_request_client_delegate.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_authentication_request_proxy.h" |
| #include "content/public/browser/web_contents.h" |
| #include "crypto/random.h" |
| #include "device/fido/authenticator_get_assertion_response.h" |
| #include "device/fido/cable/cable_discovery_data.h" |
| #include "device/fido/cable/v2_constants.h" |
| #include "device/fido/cable/v2_handshake.h" |
| #include "device/fido/discoverable_credential_metadata.h" |
| #include "device/fido/features.h" |
| #include "device/fido/fido_authenticator.h" |
| #include "device/fido/fido_constants.h" |
| #include "device/fido/fido_discovery_base.h" |
| #include "device/fido/fido_discovery_factory.h" |
| #include "device/fido/fido_request_handler_base.h" |
| #include "device/fido/fido_transport_protocol.h" |
| #include "device/fido/fido_types.h" |
| #include "device/fido/public_key_credential_descriptor.h" |
| #include "device/fido/public_key_credential_user_entity.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "extensions/common/url_pattern.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "net/base/url_util.h" |
| #include "third_party/icu/source/common/unicode/locid.h" |
| #include "ui/base/page_transition_types.h" |
| #include "ui/base/window_open_disposition.h" |
| #include "ui/gfx/native_widget_types.h" |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "chrome/browser/webauthn/chrome_authenticator_request_delegate_mac.h" |
| #include "device/fido/mac/credential_metadata.h" |
| #include "device/fido/mac/icloud_keychain.h" |
| #include "third_party/icu/source/i18n/unicode/timezone.h" |
| #include "ui/views/widget/widget.h" |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "chrome/browser/webauthn/local_credential_management_win.h" |
| #include "device/fido/win/authenticator.h" |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "chromeos/components/webauthn/webauthn_request_registrar.h" |
| #include "ui/aura/window.h" |
| #endif |
| |
| namespace { |
| |
| ChromeAuthenticatorRequestDelegate::TestObserver* g_observer = nullptr; |
| |
| static constexpr char kGoogleRpId[] = "google.com"; |
| |
| // Returns true iff the credential is reported as being present on the platform |
| // authenticator (i.e. it is not a phone or icloud credential). |
| bool IsCredentialFromPlatformAuthenticator( |
| device::DiscoverableCredentialMetadata cred) { |
| return cred.source != device::AuthenticatorType::kICloudKeychain && |
| cred.source != device::AuthenticatorType::kPhone; |
| } |
| |
| // Returns true iff |user_id| starts with the prefix reserved for passkeys used |
| // to authenticate to Google services. |
| bool UserIdHasGooglePasskeyAuthPrefix(const std::vector<uint8_t>& user_id) { |
| constexpr std::string_view kPrefix = "GOOGLE_ACCOUNT:"; |
| if (user_id.size() < kPrefix.size()) { |
| return false; |
| } |
| return memcmp(user_id.data(), kPrefix.data(), kPrefix.size()) == 0; |
| } |
| |
| // Filters |passkeys| to only contain credentials that are used to authenticate |
| // to Google services. |
| void FilterGoogleAuthPasskeys( |
| std::vector<device::DiscoverableCredentialMetadata>* passkeys) { |
| std::erase_if(*passkeys, [](const auto& passkey) { |
| return IsCredentialFromPlatformAuthenticator(passkey) && |
| !UserIdHasGooglePasskeyAuthPrefix(passkey.user.id); |
| }); |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| const char kWebAuthnTouchIdLastUsed[] = "webauthn.touchid.last_used"; |
| |
| // kMacOsRecentlyUsedMaxDays specifies how recently the macOS profile |
| // authenticator must have been used (for the current profile) to be considered |
| // "actively" used. Chrome may default to the profile authenticator in more |
| // cases if it is being actively used. |
| const int kMacOsRecentlyUsedMaxDays = 31; |
| #endif |
| |
| // CableLinkingEventHandler handles linking information sent by caBLEv2 |
| // authenticators. This linking information can come after the WebAuthn |
| // operation has resolved and thus after the |
| // `ChromeAuthenticatorRequestDelegate` has been destroyed. Thus this object is |
| // owned by the callback itself, and can save linking information until the |
| // point where the `Profile` itself is destroyed. |
| class CableLinkingEventHandler : public ProfileObserver { |
| public: |
| explicit CableLinkingEventHandler(Profile* profile) : profile_(profile) { |
| profile_->AddObserver(this); |
| } |
| |
| ~CableLinkingEventHandler() override { |
| if (profile_) { |
| profile_->RemoveObserver(this); |
| profile_ = nullptr; |
| } |
| } |
| |
| void OnNewCablePairing(std::unique_ptr<device::cablev2::Pairing> pairing) { |
| if (!profile_) { |
| FIDO_LOG(DEBUG) << "Linking event was discarded because it was received " |
| "after the profile was destroyed."; |
| return; |
| } |
| |
| // Drop linking in Incognito sessions. While an argument could be made that |
| // it's OK to persist them, this seems like the safe option. |
| if (profile_->IsOffTheRecord()) { |
| FIDO_LOG(DEBUG) << "Linking event was discarded because the profile is " |
| "Off The Record."; |
| return; |
| } |
| |
| cablev2::AddPairing(profile_, std::move(pairing)); |
| } |
| |
| // ProfileObserver: |
| |
| void OnProfileWillBeDestroyed(Profile* profile) override { |
| DCHECK_EQ(profile, profile_); |
| profile_->RemoveObserver(this); |
| profile_ = nullptr; |
| } |
| |
| private: |
| raw_ptr<Profile> profile_; |
| }; |
| |
| #if BUILDFLAG(IS_MAC) |
| |
| bool UserDeniedICloudKeychainPermission() { |
| const std::optional<bool> has_permission = |
| device::fido::icloud_keychain::HasPermission(); |
| return has_permission && has_permission.value() == false; |
| } |
| |
| bool AccountHasPasskeys(Profile* profile) { |
| webauthn::PasskeyModel* passkey_model = |
| PasskeyModelFactory::GetInstance()->GetForProfile(profile); |
| CHECK(passkey_model); |
| return !passkey_model->IsEmpty(); |
| } |
| |
| bool AccountHasNonAppleDevice(Profile* profile) { |
| syncer::DeviceInfoSyncService* const sync_service = |
| DeviceInfoSyncServiceFactory::GetForProfile(profile); |
| if (!sync_service) { |
| return false; |
| } |
| |
| syncer::DeviceInfoTracker* const tracker = |
| sync_service->GetDeviceInfoTracker(); |
| const std::vector<const syncer::DeviceInfo*> devices = |
| tracker->GetAllDeviceInfo(); |
| |
| return base::ranges::any_of(devices, [](const auto* device) { |
| switch (device->os_type()) { |
| case syncer::DeviceInfo::OsType::kIOS: |
| case syncer::DeviceInfo::OsType::kMac: |
| return false; |
| default: |
| return true; |
| } |
| }); |
| } |
| |
| bool EnclaveCanBeDefault(Profile* profile) { |
| if (AccountHasPasskeys(profile)) { |
| FIDO_LOG(EVENT) |
| << "Enclave can be default because account already has passkeys."; |
| return true; |
| } |
| |
| if (AccountHasNonAppleDevice(profile)) { |
| FIDO_LOG(EVENT) |
| << "Enclave can be default because non-Apple device found in Sync."; |
| return true; |
| } |
| |
| if (!device::fido::icloud_keychain::IsSupported()) { |
| FIDO_LOG(EVENT) |
| << "Enclave can be default because iCloud Keychain isn't supported."; |
| return true; |
| } |
| |
| if (!IsICloudDriveEnabled()) { |
| FIDO_LOG(EVENT) |
| << "Enclave can be default because iCloud Drive isn't enabled."; |
| return true; |
| } |
| |
| if (UserDeniedICloudKeychainPermission()) { |
| FIDO_LOG(EVENT) << "Enclave can be default because iCloud Keychain " |
| "permission is denied."; |
| return true; |
| } |
| |
| FIDO_LOG(EVENT) << "Enclave cannot be the default for this request. No " |
| "enabling conditions apply."; |
| return false; |
| } |
| |
| #else |
| |
| bool EnclaveCanBeDefault(Profile* profile) { |
| return true; |
| } |
| |
| #endif |
| |
| bool SkipGpmPasskeyCreationForOwnAccount( |
| device::FidoRequestType request_type, |
| const std::string& rp_id, |
| std::string_view user_name, |
| const CoreAccountInfo& primary_account_info) { |
| // Don't let GPM create a passkey for its own account within itself. |
| // |
| // The request username is either the full email address (GAIA users) or just |
| // the local part (google.com users). |
| // |
| // Note that if the string does not contain an '@', `substr(0, npos)` will |
| // return the whole string. |
| const std::string account_email_local_part = |
| primary_account_info.email.substr(0, |
| primary_account_info.email.find('@')); |
| return request_type == device::FidoRequestType::kMakeCredential && |
| rp_id == kGoogleRpId && |
| (user_name == primary_account_info.email || |
| user_name == account_email_local_part); |
| } |
| |
| } // namespace |
| |
| std::vector<std::unique_ptr<device::cablev2::Pairing>> |
| ChromeAuthenticatorRequestDelegate::TestObserver:: |
| GetCablePairingsFromSyncedDevices() { |
| return {}; |
| } |
| |
| // static |
| void ChromeAuthenticatorRequestDelegate::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterListPref(prefs::kSecurityKeyPermitAttestation); |
| registry->RegisterIntegerPref( |
| webauthn::pref_names::kEnclaveDeclinedGPMCredentialCreationCount, 0); |
| registry->RegisterIntegerPref( |
| webauthn::pref_names::kEnclaveDeclinedGPMBootstrappingCount, 0); |
| #if BUILDFLAG(IS_WIN) |
| LocalCredentialManagementWin::RegisterProfilePrefs(registry); |
| #endif |
| #if BUILDFLAG(IS_MAC) |
| registry->RegisterStringPref( |
| webauthn::pref_names::kWebAuthnTouchIdMetadataSecretPrefName, |
| std::string()); |
| registry->RegisterStringPref(kWebAuthnTouchIdLastUsed, std::string()); |
| // This boolean preference is used as a tristate. If unset, whether or not to |
| // default to iCloud is determined based on several factors. |
| // (See `ShouldCreateInICloudKeychain`.) If set, then this preference is |
| // controlling. |
| // |
| // The default value of this preference only determines whether the toggle |
| // in settings will show as set or not when the preference hasn't been |
| // explicitly set. Since the behaviour is actually more complex than can be |
| // expressed in a boolean, this is always an approximation. |
| registry->RegisterBooleanPref( |
| prefs::kCreatePasskeysInICloudKeychain, |
| ShouldCreateInICloudKeychain( |
| RequestSource::kWebAuthentication, |
| // Whether or not the user is actively using the profile authenticator |
| // is stored in preferences, which aren't available at this time while |
| // we're still registering them. Thus we assume that they are not. |
| /*is_active_profile_authenticator_user=*/false, |
| IsICloudDriveEnabled(), |
| /*request_is_for_google_com=*/false, /*preference=*/std::nullopt)); |
| #endif |
| cablev2::RegisterProfilePrefs(registry); |
| } |
| |
| ChromeAuthenticatorRequestDelegate::ChromeAuthenticatorRequestDelegate( |
| content::RenderFrameHost* render_frame_host) |
| : render_frame_host_id_(render_frame_host->GetGlobalId()), |
| dialog_model_(base::MakeRefCounted<AuthenticatorRequestDialogModel>( |
| GetRenderFrameHost())), |
| dialog_controller_(std::make_unique<AuthenticatorRequestDialogController>( |
| dialog_model_.get(), |
| GetRenderFrameHost())) { |
| dialog_model_->observers.AddObserver(this); |
| if (g_observer) { |
| g_observer->Created(this); |
| } |
| } |
| |
| ChromeAuthenticatorRequestDelegate::~ChromeAuthenticatorRequestDelegate() { |
| // Currently, completion of the request is indicated by //content destroying |
| // this delegate. |
| dialog_model_->OnRequestComplete(); |
| dialog_model_->observers.RemoveObserver(this); |
| |
| if (g_observer) { |
| g_observer->OnDestroy(this); |
| } |
| } |
| |
| // static |
| void ChromeAuthenticatorRequestDelegate::SetGlobalObserverForTesting( |
| TestObserver* observer) { |
| CHECK(!observer || !g_observer); |
| g_observer = observer; |
| } |
| |
| base::WeakPtr<ChromeAuthenticatorRequestDelegate> |
| ChromeAuthenticatorRequestDelegate::AsWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| GPMEnclaveController* |
| ChromeAuthenticatorRequestDelegate::enclave_controller_for_testing() const { |
| return enclave_controller_.get(); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::SetRelyingPartyId( |
| const std::string& rp_id) { |
| dialog_model_->relying_party_id = rp_id; |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::SetUIPresentation( |
| UIPresentation ui_presentation) { |
| dialog_controller_->set_ui_presentation(ui_presentation); |
| } |
| |
| bool ChromeAuthenticatorRequestDelegate::DoesBlockRequestOnFailure( |
| InterestingFailureReason reason) { |
| if (!webauthn_ui_enabled()) { |
| return false; |
| } |
| |
| // If the UI was already in the state where we asked the user to complete the |
| // transaction on the other device then any errors are immediately resolved. |
| // Very likely the user canceled on the phone and doesn't want to see another |
| // error UI on the desktop. |
| if (cable_device_ready_) { |
| return false; |
| } |
| |
| switch (reason) { |
| case InterestingFailureReason::kTimeout: |
| dialog_controller_->OnRequestTimeout(); |
| break; |
| case InterestingFailureReason::kKeyNotRegistered: |
| dialog_controller_->OnActivatedKeyNotRegistered(); |
| break; |
| case InterestingFailureReason::kKeyAlreadyRegistered: |
| dialog_controller_->OnActivatedKeyAlreadyRegistered(); |
| break; |
| case InterestingFailureReason::kSoftPINBlock: |
| dialog_controller_->OnSoftPINBlock(); |
| break; |
| case InterestingFailureReason::kHardPINBlock: |
| dialog_controller_->OnHardPINBlock(); |
| break; |
| case InterestingFailureReason::kAuthenticatorRemovedDuringPINEntry: |
| dialog_controller_->OnAuthenticatorRemovedDuringPINEntry(); |
| break; |
| case InterestingFailureReason::kAuthenticatorMissingResidentKeys: |
| dialog_controller_->OnAuthenticatorMissingResidentKeys(); |
| break; |
| case InterestingFailureReason::kAuthenticatorMissingUserVerification: |
| dialog_controller_->OnAuthenticatorMissingUserVerification(); |
| break; |
| case InterestingFailureReason::kAuthenticatorMissingLargeBlob: |
| dialog_controller_->OnAuthenticatorMissingLargeBlob(); |
| break; |
| case InterestingFailureReason::kNoCommonAlgorithms: |
| dialog_controller_->OnNoCommonAlgorithms(); |
| break; |
| case InterestingFailureReason::kStorageFull: |
| dialog_controller_->OnAuthenticatorStorageFull(); |
| break; |
| case InterestingFailureReason::kUserConsentDenied: |
| dialog_controller_->OnUserConsentDenied(); |
| break; |
| case InterestingFailureReason::kWinUserCancelled: |
| return dialog_controller_->OnWinUserCancelled(); |
| case InterestingFailureReason::kHybridTransportError: |
| return dialog_controller_->OnHybridTransportError(); |
| case InterestingFailureReason::kNoPasskeys: |
| return dialog_controller_->OnNoPasskeys(); |
| case InterestingFailureReason::kEnclaveError: |
| return dialog_controller_->OnEnclaveError(); |
| case InterestingFailureReason::kEnclaveCancel: |
| dialog_model_->CancelAuthenticatorRequest(); |
| break; |
| } |
| return true; |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::OnTransactionSuccessful( |
| RequestSource request_source, |
| device::FidoRequestType request_type, |
| device::AuthenticatorType authenticator_type) { |
| if (request_source != RequestSource::kWebAuthentication) { |
| return; |
| } |
| #if BUILDFLAG(IS_MAC) |
| if (authenticator_type == device::AuthenticatorType::kTouchID) { |
| profile()->GetPrefs()->SetString( |
| kWebAuthnTouchIdLastUsed, |
| base::UnlocalizedTimeFormatWithPattern(base::Time::Now(), "yyyy-MM-dd", |
| icu::TimeZone::getGMT())); |
| webauthn::user_actions::RecordChromeProfileSuccess(); |
| } |
| if (authenticator_type == device::AuthenticatorType::kICloudKeychain) { |
| webauthn::user_actions::RecordICloudSuccess(); |
| } |
| |
| dialog_controller_->RecordMacOsSuccessHistogram(request_type, |
| authenticator_type); |
| #elif BUILDFLAG(IS_WIN) |
| if (authenticator_type == device::AuthenticatorType::kWinNative) { |
| webauthn::user_actions::RecordWindowsHelloSuccess(); |
| } |
| #endif // BUILDFLAG(IS_MAC) |
| if (authenticator_type == device::AuthenticatorType::kEnclave) { |
| if (dialog_model_->in_onboarding_flow) { |
| RecordOnboardingEvent(webauthn::metrics::OnboardingEvents::kSucceeded); |
| } |
| switch (request_type) { |
| case device::FidoRequestType::kGetAssertion: |
| RecordGPMGetAssertionEvent( |
| webauthn::metrics::GPMGetAssertionEvents::kSuccess); |
| break; |
| case device::FidoRequestType::kMakeCredential: |
| RecordGPMMakeCredentialEvent( |
| webauthn::metrics::GPMMakeCredentialEvents::kSuccess); |
| break; |
| } |
| webauthn::user_actions::RecordGpmSuccess(); |
| } |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::RegisterActionCallbacks( |
| base::OnceClosure cancel_callback, |
| base::RepeatingClosure start_over_callback, |
| AccountPreselectedCallback account_preselected_callback, |
| device::FidoRequestHandlerBase::RequestCallback request_callback, |
| base::RepeatingClosure bluetooth_adapter_power_on_callback, |
| base::RepeatingCallback< |
| void(device::FidoRequestHandlerBase::BlePermissionCallback)> |
| request_ble_permission_callback) { |
| request_callback_ = request_callback; |
| cancel_callback_ = std::move(cancel_callback); |
| start_over_callback_ = std::move(start_over_callback); |
| account_preselected_callback_ = std::move(account_preselected_callback); |
| |
| dialog_controller_->SetRequestCallback(request_callback); |
| dialog_controller_->SetAccountPreselectedCallback( |
| account_preselected_callback_); |
| dialog_controller_->SetBluetoothAdapterPowerOnCallback( |
| bluetooth_adapter_power_on_callback); |
| dialog_controller_->SetRequestBlePermissionCallback( |
| request_ble_permission_callback); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::ConfigureDiscoveries( |
| const url::Origin& origin, |
| const std::string& rp_id, |
| RequestSource request_source, |
| device::FidoRequestType request_type, |
| std::optional<device::ResidentKeyRequirement> resident_key_requirement, |
| device::UserVerificationRequirement user_verification_requirement, |
| std::optional<std::string_view> user_name, |
| base::span<const device::CableDiscoveryData> pairings_from_extension, |
| bool browser_provided_passkeys_available, |
| device::FidoDiscoveryFactory* discovery_factory) { |
| DCHECK(request_type == device::FidoRequestType::kGetAssertion || |
| resident_key_requirement.has_value()); |
| request_type_ = request_type; |
| user_verification_requirement_ = user_verification_requirement; |
| |
| // Without the UI enabled, discoveries like caBLE, Android AOA, iCloud |
| // keychain, and the enclave, don't make sense. |
| if (!webauthn_ui_enabled()) { |
| return; |
| } |
| |
| // Configure the enclave authenticator. |
| if (browser_provided_passkeys_available && !IsVirtualEnvironmentEnabled() && |
| request_source == RequestSource::kWebAuthentication) { |
| // Creating credentials in GPM can be disabled by policy, but get() is |
| // always allowed. |
| const bool enclave_create_enabled = |
| profile()->GetPrefs()->GetBoolean( |
| password_manager::prefs::kCredentialsEnableService) && |
| profile()->GetPrefs()->GetBoolean( |
| password_manager::prefs::kCredentialsEnablePasskeys); |
| if (dialog_controller_->ui_presentation() == |
| UIPresentation::kPasskeyUpgrade && |
| enclave_create_enabled) { |
| // Set up the upgrade request controller. This handles enclave |
| // transactions in place of the "regular" GPMEnclaveController. |
| CHECK(!enclave_controller_); |
| PasskeyUpgradeRequestController::GetOrCreateForCurrentDocument( |
| GetRenderFrameHost()) |
| ->InitializeEnclaveRequestCallback(discovery_factory); |
| discovery_factory->set_network_context_factory(base::BindRepeating([]() { |
| return SystemNetworkContextManager::GetInstance()->GetContext(); |
| })); |
| } else if (request_type == device::FidoRequestType::kGetAssertion || |
| enclave_create_enabled) { |
| // Set up the "regular" enclave controller. |
| auto* const identity_manager = IdentityManagerFactory::GetForProfile( |
| profile()->GetOriginalProfile()); |
| const auto consent = signin::ConsentLevel::kSignin; |
| if (identity_manager->HasPrimaryAccount(consent)) { |
| CoreAccountInfo account_info = |
| identity_manager->GetPrimaryAccountInfo(consent); |
| if (SkipGpmPasskeyCreationForOwnAccount( |
| request_type, rp_id, user_name.value_or(""), account_info)) { |
| FIDO_LOG(EVENT) |
| << "Creation in GPM not offered (same primary account)"; |
| } else { |
| enclave_controller_ = std::make_unique<GPMEnclaveController>( |
| GetRenderFrameHost(), dialog_model_.get(), rp_id, request_type, |
| user_verification_requirement, |
| tick_clock_ ? tick_clock_ : base::DefaultTickClock::GetInstance(), |
| timer_task_runner_, std::move(pending_trusted_vault_connection_)); |
| } |
| } |
| } else { |
| FIDO_LOG(EVENT) |
| << "Enclave unavailable for creating passkeys due to policy."; |
| } |
| } |
| |
| const bool cable_extension_permitted = ShouldPermitCableExtension(origin); |
| const bool cable_extension_provided = |
| cable_extension_permitted && !pairings_from_extension.empty(); |
| |
| if (g_observer) { |
| for (const auto& pairing : pairings_from_extension) { |
| if (pairing.version == device::CableDiscoveryData::Version::V2) { |
| g_observer->CableV2ExtensionSeen(pairing.v2->server_link_data); |
| } |
| } |
| |
| g_observer->ConfiguringCable(request_type); |
| } |
| |
| #if BUILDFLAG(IS_LINUX) |
| // No caBLEv1 on Linux. It tends to crash bluez. |
| if (base::Contains(pairings_from_extension, |
| device::CableDiscoveryData::Version::V1, |
| &device::CableDiscoveryData::version)) { |
| pairings_from_extension = base::span<const device::CableDiscoveryData>(); |
| } |
| #endif |
| |
| std::vector<device::CableDiscoveryData> pairings; |
| if (cable_extension_permitted) { |
| pairings.insert(pairings.end(), pairings_from_extension.begin(), |
| pairings_from_extension.end()); |
| } |
| const bool cable_extension_accepted = !pairings.empty(); |
| const bool cablev2_extension_provided = |
| base::Contains(pairings, device::CableDiscoveryData::Version::V2, |
| &device::CableDiscoveryData::version); |
| |
| std::vector<std::unique_ptr<device::cablev2::Pairing>> paired_phones; |
| base::RepeatingCallback<void(std::unique_ptr<device::cablev2::Pairing>)> |
| contact_phone_callback; |
| if (base::FeatureList::IsEnabled(device::kWebAuthnHybridLinking) && |
| (!cable_extension_provided || |
| base::FeatureList::IsEnabled(device::kWebAuthCableExtensionAnywhere))) { |
| std::unique_ptr<cablev2::KnownDevices> known_devices = |
| cablev2::KnownDevices::FromProfile(profile()); |
| if (g_observer) { |
| known_devices->synced_devices = |
| g_observer->GetCablePairingsFromSyncedDevices(); |
| } |
| can_use_synced_phone_passkeys_ = !known_devices->synced_devices.empty(); |
| paired_phones = cablev2::MergeDevices(std::move(known_devices), |
| &icu::Locale::getDefault()); |
| |
| // The debug log displays in reverse order, so the headline is emitted after |
| // the names. |
| for (const auto& pairing : paired_phones) { |
| FIDO_LOG(DEBUG) << "• " << pairing->name << " " << pairing->last_updated |
| << " priority:" << pairing->channel_priority; |
| } |
| FIDO_LOG(DEBUG) << "Found " << paired_phones.size() << " caBLEv2 devices"; |
| |
| if (!paired_phones.empty()) { |
| contact_phone_callback = discovery_factory->get_cable_contact_callback(); |
| } |
| } |
| |
| const bool non_extension_cablev2_enabled = |
| (!cable_extension_permitted || |
| (!cable_extension_provided && |
| request_type == device::FidoRequestType::kGetAssertion) || |
| (request_type == device::FidoRequestType::kMakeCredential && |
| resident_key_requirement.has_value() && |
| resident_key_requirement.value() != |
| device::ResidentKeyRequirement::kDiscouraged) || |
| base::FeatureList::IsEnabled(device::kWebAuthCableExtensionAnywhere)); |
| |
| std::optional<std::array<uint8_t, device::cablev2::kQRKeySize>> |
| qr_generator_key; |
| std::optional<std::string> qr_string; |
| if (non_extension_cablev2_enabled || cablev2_extension_provided) { |
| // A QR key is generated for all caBLEv2 cases but whether the QR code is |
| // displayed is up to the UI. |
| qr_generator_key.emplace(); |
| crypto::RandBytes(*qr_generator_key); |
| qr_string = device::cablev2::qr::Encode(*qr_generator_key, request_type); |
| |
| auto linking_handler = |
| std::make_unique<CableLinkingEventHandler>(profile()); |
| discovery_factory->set_cable_pairing_callback( |
| base::BindRepeating(&CableLinkingEventHandler::OnNewCablePairing, |
| std::move(linking_handler))); |
| discovery_factory->set_cable_invalidated_pairing_callback( |
| base::BindRepeating( |
| &ChromeAuthenticatorRequestDelegate::OnInvalidatedCablePairing, |
| weak_ptr_factory_.GetWeakPtr())); |
| discovery_factory->set_cable_event_callback( |
| base::BindRepeating(&ChromeAuthenticatorRequestDelegate::OnCableEvent, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| if (SystemNetworkContextManager::GetInstance()) { |
| // caBLE and the enclave depend on the network context factory. |
| // TODO(nsatragno): this should probably use a storage partition network |
| // context instead. See the SystemNetworkContextManager class comments. |
| discovery_factory->set_network_context_factory(base::BindRepeating([]() { |
| return SystemNetworkContextManager::GetInstance()->GetContext(); |
| })); |
| } |
| |
| if (cable_extension_accepted || non_extension_cablev2_enabled) { |
| std::optional<bool> extension_is_v2; |
| if (cable_extension_provided) { |
| extension_is_v2 = cablev2_extension_provided; |
| } |
| dialog_controller_->set_cable_transport_info( |
| extension_is_v2, std::move(paired_phones), |
| std::move(contact_phone_callback), qr_string); |
| discovery_factory->set_cable_data(request_type, std::move(pairings), |
| qr_generator_key); |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| ConfigureNSWindow(discovery_factory); |
| #endif |
| |
| if (enclave_controller_) { |
| enclave_controller_->ConfigureDiscoveries(discovery_factory); |
| } |
| |
| dialog_controller_->set_is_non_webauthn_request( |
| request_source != RequestSource::kWebAuthentication); |
| |
| #if BUILDFLAG(IS_MAC) |
| ConfigureICloudKeychain(request_source, rp_id); |
| #endif |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::SetHints( |
| const AuthenticatorRequestClientDelegate::Hints& hints) { |
| if (g_observer) { |
| g_observer->HintsSet(hints); |
| } |
| dialog_controller_->SetHints(hints); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::SelectAccount( |
| std::vector<device::AuthenticatorGetAssertionResponse> responses, |
| base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)> |
| callback) { |
| if (!webauthn_ui_enabled()) { |
| // Requests with UI disabled should never reach account selection. |
| DCHECK(IsVirtualEnvironmentEnabled()); |
| |
| // The browser is being automated. Select the first credential to support |
| // automation of discoverable credentials. |
| // TODO(crbug.com/40639383): Provide a way to determine which account gets |
| // picked. |
| std::move(callback).Run(std::move(responses.at(0))); |
| return; |
| } |
| |
| if (g_observer) { |
| g_observer->AccountSelectorShown(responses); |
| std::move(callback).Run(std::move(responses.at(0))); |
| return; |
| } |
| |
| dialog_controller_->SelectAccount(std::move(responses), std::move(callback)); |
| } |
| |
| bool ChromeAuthenticatorRequestDelegate::webauthn_ui_enabled() const { |
| return dialog_controller_->ui_presentation() != UIPresentation::kDisabled; |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::SetAmbientCredentialTypes( |
| int credential_type_flags) { |
| ambient_credential_types_ = credential_type_flags; |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::SetCredentialIdFilter( |
| std::vector<device::PublicKeyCredentialDescriptor> credential_list) { |
| credential_filter_ = std::move(credential_list); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::SetUserEntityForMakeCredentialRequest( |
| const device::PublicKeyCredentialUserEntity& user_entity) { |
| dialog_model_->user_entity = user_entity; |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::OnTransportAvailabilityEnumerated( |
| device::FidoRequestHandlerBase::TransportAvailabilityInfo data) { |
| if (g_observer) { |
| g_observer->OnPreTransportAvailabilityEnumerated(this); |
| } |
| |
| if (!webauthn_ui_enabled()) { |
| return; |
| } |
| |
| const bool delay_ui_for_gpm = |
| enclave_controller_ && !enclave_controller_->ready_for_ui(); |
| if (delay_ui_for_gpm) { |
| // Delay showing UI until GPM state is loaded. It's only after this |
| // point that we know whether GPM will be active for this request or not. |
| pending_transport_availability_info_ = std::make_unique< |
| device::FidoRequestHandlerBase::TransportAvailabilityInfo>( |
| std::move(data)); |
| return; |
| } |
| |
| ShowUI(std::move(data)); |
| } |
| |
| bool ChromeAuthenticatorRequestDelegate::EmbedderControlsAuthenticatorDispatch( |
| const device::FidoAuthenticator& authenticator) { |
| // Decide whether the //device/fido code should dispatch the current |
| // request to an authenticator immediately after it has been |
| // discovered, or whether the embedder/UI takes charge of that by |
| // invoking its RequestCallback. |
| if (!webauthn_ui_enabled()) { |
| // There is no UI to handle request dispatch. |
| return false; |
| } |
| if (authenticator.GetType() == device::AuthenticatorType::kEnclave) { |
| return false; |
| } |
| |
| if (dialog_controller_->ui_presentation() == UIPresentation::kAutofill && |
| (dialog_model_->step() == |
| AuthenticatorRequestDialogModel::Step::kPasskeyAutofill || |
| dialog_model_->step() == |
| AuthenticatorRequestDialogModel::Step::kNotStarted)) { |
| // There is an active conditional request that is not showing any UI. The UI |
| // will dispatch to any plugged in authenticators after the user selects an |
| // option. |
| return true; |
| } |
| auto transport = authenticator.AuthenticatorTransport(); |
| return !transport || // Windows |
| *transport == device::FidoTransportProtocol::kInternal; |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::FidoAuthenticatorAdded( |
| const device::FidoAuthenticator& authenticator) { |
| if (!webauthn_ui_enabled()) { |
| return; |
| } |
| |
| dialog_controller_->AddAuthenticator(authenticator); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::FidoAuthenticatorRemoved( |
| std::string_view authenticator_id) { |
| if (!webauthn_ui_enabled()) { |
| return; |
| } |
| |
| dialog_controller_->RemoveAuthenticator(authenticator_id); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::BluetoothAdapterStatusChanged( |
| device::FidoRequestHandlerBase::BleStatus ble_status) { |
| dialog_controller_->BluetoothAdapterStatusChanged(ble_status); |
| } |
| |
| bool ChromeAuthenticatorRequestDelegate::SupportsPIN() const { |
| return true; |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::CollectPIN( |
| CollectPINOptions options, |
| base::OnceCallback<void(std::u16string)> provide_pin_cb) { |
| dialog_controller_->CollectPIN(options.reason, options.error, |
| options.min_pin_length, options.attempts, |
| std::move(provide_pin_cb)); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::StartBioEnrollment( |
| base::OnceClosure next_callback) { |
| dialog_controller_->StartInlineBioEnrollment(std::move(next_callback)); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::OnSampleCollected( |
| int bio_samples_remaining) { |
| dialog_controller_->OnSampleCollected(bio_samples_remaining); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::FinishCollectToken() { |
| dialog_controller_->FinishCollectToken(); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::OnRetryUserVerification(int attempts) { |
| dialog_controller_->OnRetryUserVerification(attempts); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::OnStartOver() { |
| DCHECK(start_over_callback_); |
| dialog_model_->generation++; |
| if (g_observer) { |
| g_observer->PreStartOver(); |
| } |
| start_over_callback_.Run(); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::OnModelDestroyed( |
| AuthenticatorRequestDialogModel* model) { |
| DCHECK_EQ(model, dialog_model_.get()); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::OnCancelRequest() { |
| // |cancel_callback_| must be invoked at most once as invocation of |
| // |cancel_callback_| will destroy |this|. |
| DCHECK(cancel_callback_); |
| std::move(cancel_callback_).Run(); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::OnManageDevicesClicked() { |
| content::WebContents* web_contents = |
| content::WebContents::FromRenderFrameHost(GetRenderFrameHost()); |
| Browser* browser = chrome::FindBrowserWithTab(web_contents); |
| if (browser) { |
| NavigateParams params(browser, |
| GURL("chrome://settings/securityKeys/phones"), |
| ui::PageTransition::PAGE_TRANSITION_AUTO_TOPLEVEL); |
| params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| Navigate(¶ms); |
| } |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::SetTrustedVaultConnectionForTesting( |
| std::unique_ptr<trusted_vault::TrustedVaultConnection> connection) { |
| pending_trusted_vault_connection_ = std::move(connection); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::SetMockTimeForTesting( |
| base::TickClock const* tick_clock, |
| scoped_refptr<base::SequencedTaskRunner> task_runner) { |
| CHECK(!enclave_controller_); |
| tick_clock_ = tick_clock; |
| timer_task_runner_ = std::move(task_runner); |
| } |
| |
| content::RenderFrameHost* |
| ChromeAuthenticatorRequestDelegate::GetRenderFrameHost() const { |
| content::RenderFrameHost* ret = |
| content::RenderFrameHost::FromID(render_frame_host_id_); |
| DCHECK(ret); |
| return ret; |
| } |
| |
| content::BrowserContext* ChromeAuthenticatorRequestDelegate::GetBrowserContext() |
| const { |
| return GetRenderFrameHost()->GetBrowserContext(); |
| } |
| |
| Profile* ChromeAuthenticatorRequestDelegate::profile() const { |
| return Profile::FromBrowserContext(GetRenderFrameHost()->GetBrowserContext()); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::ShowUI( |
| device::FidoRequestHandlerBase::TransportAvailabilityInfo tai) { |
| if (can_use_synced_phone_passkeys_ || |
| (enclave_controller_ && enclave_controller_->is_active())) { |
| GetPhoneContactableGpmPasskeysForRpId(&tai.recognized_credentials); |
| } |
| FilterRecognizedCredentials(&tai); |
| |
| if (g_observer) { |
| g_observer->OnTransportAvailabilityEnumerated(this, &tai); |
| } |
| |
| if (dialog_model_->step() != |
| AuthenticatorRequestDialogModel::Step::kNotStarted) { |
| return; |
| } |
| |
| // At the time of writing we don't support GPM passkeys on iOS, so we want to |
| // avoid defaulting to GPM for macOS users who likely have an iPhone. But on |
| // all other platforms, GPM should be the default. |
| dialog_controller_->set_enclave_can_be_default( |
| EnclaveCanBeDefault(profile())); |
| |
| dialog_controller_->set_ambient_credential_types(ambient_credential_types_); |
| |
| dialog_controller_->StartFlow(std::move(tai)); |
| |
| if (g_observer) { |
| g_observer->UIShown(this); |
| } |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::OnReadyForUI() { |
| if (!pending_transport_availability_info_) { |
| return; |
| } |
| |
| auto pending_transport_availability_info = |
| std::move(pending_transport_availability_info_); |
| pending_transport_availability_info_.reset(); |
| |
| ShowUI(std::move(*pending_transport_availability_info)); |
| } |
| |
| bool ChromeAuthenticatorRequestDelegate::ShouldPermitCableExtension( |
| const url::Origin& origin) { |
| if (base::FeatureList::IsEnabled(device::kWebAuthCableExtensionAnywhere)) { |
| return true; |
| } |
| |
| // Because the future of the caBLE extension might be that we transition |
| // everything to QR-code or sync-based pairing, we don't want use of the |
| // extension to spread without consideration. Therefore it's limited to |
| // origins that are already depending on it and test sites. |
| if (origin.DomainIs("google.com")) { |
| return true; |
| } |
| |
| const GURL test_site("https://webauthndemo.appspot.com"); |
| DCHECK(test_site.is_valid()); |
| return origin.IsSameOriginWith(test_site); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::OnInvalidatedCablePairing( |
| std::unique_ptr<device::cablev2::Pairing> failed_pairing) { |
| // A pairing was reported to be invalid. Delete it unless it came from Sync, |
| // in which case there's nothing to be done. |
| cablev2::DeletePairingByPublicKey(profile()->GetPrefs(), |
| failed_pairing->peer_public_key_x962); |
| |
| // Contact the next phone with the same name, if any, given that no |
| // notification has been sent. |
| dialog_controller_->OnPhoneContactFailed(failed_pairing->name); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::OnCableEvent( |
| device::cablev2::Event event) { |
| if (event == device::cablev2::Event::kReady) { |
| cable_device_ready_ = true; |
| } |
| |
| dialog_controller_->OnCableEvent(event); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::GetPhoneContactableGpmPasskeysForRpId( |
| std::vector<device::DiscoverableCredentialMetadata>* passkeys) { |
| device::AuthenticatorType type; |
| std::vector<sync_pb::WebauthnCredentialSpecifics> credentials; |
| |
| if (enclave_controller_ && enclave_controller_->is_active()) { |
| credentials = enclave_controller_->creds(); |
| type = device::AuthenticatorType::kEnclave; |
| } else { |
| webauthn::PasskeyModel* passkey_model = |
| PasskeyModelFactory::GetInstance()->GetForProfile(profile()); |
| CHECK(passkey_model); |
| credentials = passkey_model->GetPasskeysForRelyingPartyId( |
| dialog_model_->relying_party_id); |
| type = device::AuthenticatorType::kPhone; |
| } |
| |
| for (const sync_pb::WebauthnCredentialSpecifics& passkey : credentials) { |
| passkeys->emplace_back( |
| type, passkey.rp_id(), |
| std::vector<uint8_t>(passkey.credential_id().begin(), |
| passkey.credential_id().end()), |
| device::PublicKeyCredentialUserEntity( |
| std::vector<uint8_t>(passkey.user_id().begin(), |
| passkey.user_id().end()), |
| passkey.user_name(), passkey.user_display_name())); |
| } |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::FilterRecognizedCredentials( |
| device::FidoRequestHandlerBase::TransportAvailabilityInfo* tai) { |
| if (dialog_model()->relying_party_id == kGoogleRpId && |
| tai->has_empty_allow_list && |
| base::ranges::any_of(tai->recognized_credentials, |
| IsCredentialFromPlatformAuthenticator)) { |
| // Regrettably, Chrome will create webauthn credentials for things other |
| // than authentication (e.g. credit card autofill auth) under the rp id |
| // "google.com". To differentiate those credentials from actual passkeys you |
| // can use to sign in, Google adds a prefix to the user id. |
| // This code filter passkeys that do not match that prefix. |
| FilterGoogleAuthPasskeys(&tai->recognized_credentials); |
| if (tai->has_platform_authenticator_credential == |
| device::FidoRequestHandlerBase::RecognizedCredential:: |
| kHasRecognizedCredential && |
| base::ranges::none_of(tai->recognized_credentials, |
| IsCredentialFromPlatformAuthenticator)) { |
| tai->has_platform_authenticator_credential = device:: |
| FidoRequestHandlerBase::RecognizedCredential::kNoRecognizedCredential; |
| } |
| } |
| |
| if (!credential_filter_.empty()) { |
| std::vector<device::DiscoverableCredentialMetadata> filtered_list; |
| for (auto& platform_credential : tai->recognized_credentials) { |
| for (auto& filter_credential : credential_filter_) { |
| if (platform_credential.cred_id == filter_credential.id) { |
| filtered_list.push_back(platform_credential); |
| break; |
| } |
| } |
| } |
| tai->recognized_credentials = std::move(filtered_list); |
| } |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| // static |
| std::optional<int> ChromeAuthenticatorRequestDelegate::DaysSinceDate( |
| const std::string& formatted_date, |
| const base::Time now) { |
| int year, month, day_of_month; |
| // sscanf will ignore trailing garbage, but we don't need to be strict here. |
| if (sscanf(formatted_date.c_str(), "%u-%u-%u", &year, &month, |
| &day_of_month) != 3) { |
| return std::nullopt; |
| } |
| |
| const base::Time::Exploded exploded = { |
| .year = year, .month = month, .day_of_month = day_of_month}; |
| |
| base::Time t; |
| if (!base::Time::FromUTCExploded(exploded, &t) || now < t) { |
| return std::nullopt; |
| } |
| |
| const base::TimeDelta difference = now - t; |
| return difference.InDays(); |
| } |
| |
| // static |
| std::optional<bool> ChromeAuthenticatorRequestDelegate::GetICloudKeychainPref( |
| const PrefService* prefs) { |
| const PrefService::Preference* pref = |
| prefs->FindPreference(prefs::kCreatePasskeysInICloudKeychain); |
| if (pref->IsDefaultValue()) { |
| return std::nullopt; |
| } |
| return pref->GetValue()->GetBool(); |
| } |
| |
| // static |
| bool ChromeAuthenticatorRequestDelegate::IsActiveProfileAuthenticatorUser( |
| const PrefService* prefs) { |
| const std::string& last_used = prefs->GetString(kWebAuthnTouchIdLastUsed); |
| if (last_used.empty()) { |
| return false; |
| } |
| const std::optional<int> days = DaysSinceDate(last_used, base::Time::Now()); |
| return days.has_value() && days.value() <= kMacOsRecentlyUsedMaxDays; |
| } |
| |
| // static |
| bool ChromeAuthenticatorRequestDelegate::ShouldCreateInICloudKeychain( |
| RequestSource request_source, |
| bool is_active_profile_authenticator_user, |
| bool has_icloud_drive_enabled, |
| bool request_is_for_google_com, |
| std::optional<bool> preference) { |
| // Secure Payment Confirmation and credit-card autofill continue to use |
| // the profile authenticator. |
| if (request_source != RequestSource::kWebAuthentication) { |
| return false; |
| } |
| if (preference.has_value()) { |
| return *preference; |
| } |
| const base::Feature* feature; |
| if (request_is_for_google_com) { |
| feature = &device::kWebAuthnICloudKeychainForGoogle; |
| } else { |
| if (is_active_profile_authenticator_user) { |
| if (has_icloud_drive_enabled) { |
| feature = &device::kWebAuthnICloudKeychainForActiveWithDrive; |
| } else { |
| feature = &device::kWebAuthnICloudKeychainForActiveWithoutDrive; |
| } |
| } else { |
| if (has_icloud_drive_enabled) { |
| feature = &device::kWebAuthnICloudKeychainForInactiveWithDrive; |
| } else { |
| feature = &device::kWebAuthnICloudKeychainForInactiveWithoutDrive; |
| } |
| } |
| } |
| |
| return base::FeatureList::IsEnabled(*feature); |
| } |
| |
| void ChromeAuthenticatorRequestDelegate::ConfigureNSWindow( |
| device::FidoDiscoveryFactory* discovery_factory) { |
| content::WebContents* web_contents = |
| content::WebContents::FromRenderFrameHost(GetRenderFrameHost()); |
| Browser* browser = chrome::FindBrowserWithTab(web_contents); |
| if (browser && browser->is_type_app()) { |
| // PWAs render the UI in an out-of-process window, thus there is no valid |
| // NSWindow* available in the browser process. |
| // TODO: crbug.com/364926914 - potentially do iCloud Keychain operations out |
| // of process so that they can work in PWAs. |
| return; |
| } |
| |
| // Not all contexts in which this code runs have a BrowserWindow. |
| // Notably the dialog containing a WebContents that is used for signing |
| // into a new profile does not. Thus the NSWindow is fetched more directly. |
| const views::Widget* widget = views::Widget::GetTopLevelWidgetForNativeView( |
| web_contents->GetNativeView()); |
| if (widget) { |
| const gfx::NativeWindow window = widget->GetNativeWindow(); |
| if (window) { |
| discovery_factory->set_nswindow( |
| reinterpret_cast<uintptr_t>(window.GetNativeNSWindow())); |
| } |
| } |
| } |
| void ChromeAuthenticatorRequestDelegate::ConfigureICloudKeychain( |
| RequestSource request_source, |
| const std::string& rp_id) { |
| const PrefService* prefs = profile()->GetPrefs(); |
| const bool is_icloud_drive_enabled = IsICloudDriveEnabled(); |
| const bool is_active_profile_authenticator_user = |
| IsActiveProfileAuthenticatorUser(prefs); |
| dialog_controller_->set_allow_icloud_keychain( |
| request_source == RequestSource::kWebAuthentication); |
| dialog_controller_->set_has_icloud_drive_enabled(is_icloud_drive_enabled); |
| dialog_controller_->set_is_active_profile_authenticator_user( |
| is_active_profile_authenticator_user); |
| dialog_controller_->set_should_create_in_icloud_keychain( |
| ShouldCreateInICloudKeychain( |
| request_source, is_active_profile_authenticator_user, |
| is_icloud_drive_enabled, rp_id == "google.com", |
| GetICloudKeychainPref(prefs))); |
| } |
| |
| #endif |