From 6fa2e7b98f05449291e7f2a12543daa9d67b510e Mon Sep 17 00:00:00 2001 From: Carmelo Messina Date: Thu, 9 Oct 2025 11:45:09 +0200 Subject: [PATCH] Add setting to enable Credman for passkeys: enable CredentialManager APIs for A14+ (#2296 #329) thanks to jhult --- build/cromite_patches_list.txt | 1 + ...tting-to-enable-Credman-for-passkeys.patch | 336 +++++++++++++ .../Remove-binary-blob-integrations.patch | 459 +++++++----------- 3 files changed, 511 insertions(+), 285 deletions(-) create mode 100644 build/patches/Add-setting-to-enable-Credman-for-passkeys.patch diff --git a/build/cromite_patches_list.txt b/build/cromite_patches_list.txt index 3020c30b..f6f88457 100644 --- a/build/cromite_patches_list.txt +++ b/build/cromite_patches_list.txt @@ -304,6 +304,7 @@ Android-Pixel-Perfect-Mode.patch Set-caret-blink-interval-to-default.patch Set-the-screen-frame-rate-to-60-Hz.patch Disable-Device-Attributes-API.patch +Add-setting-to-enable-Credman-for-passkeys.patch Temp-disable-UseContextSnapshot.patch # temporary or wip patches diff --git a/build/patches/Add-setting-to-enable-Credman-for-passkeys.patch b/build/patches/Add-setting-to-enable-Credman-for-passkeys.patch new file mode 100644 index 00000000..b8a771ec --- /dev/null +++ b/build/patches/Add-setting-to-enable-Credman-for-passkeys.patch @@ -0,0 +1,336 @@ +From: uazo +Date: Thu, 9 Oct 2025 09:11:01 +0000 +Subject: Add setting to enable Credman for passkeys + +Enables Credential Manager for passkey management on A14+ devices via a switch +in the settings. By default, the feature is disabled. +In other versions of Android, webauthn appears to be enabled on websites but +always returns a timeout error as per specifications. +Conditional immediate support is disabled. + +License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html +--- + .../settings/PasswordSettings.java | 27 +++++++++++++++++ + ...etting-to-enable-Credman-for-passkeys.grdp | 12 ++++++++ + .../webauthn/AuthenticatorImpl.java | 29 +++++++------------ + .../components/webauthn/WebauthnFeatures.java | 2 ++ + .../cred_man/CredManSupportProvider.java | 27 +++++------------ + .../webauthn/android/webauthn_feature_map.cc | 1 + + components/webauthn/features.cc | 4 +++ + components/webauthn/features.h | 3 ++ + device/fido/features.cc | 2 ++ + .../authentication_credentials_container.cc | 5 ++++ + 10 files changed, 75 insertions(+), 37 deletions(-) + create mode 100644 chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Add-setting-to-enable-Credman-for-passkeys.grdp + +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java +@@ -10,6 +10,7 @@ import android.app.Activity; + import android.content.Context; + import android.content.Intent; + import android.net.Uri; ++import android.os.Build; + import android.os.Bundle; + import android.text.SpannableString; + import android.text.style.ForegroundColorSpan; +@@ -64,6 +65,10 @@ import org.chromium.chrome.browser.ui.messages.snackbar.INeedSnackbarManager; + import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar; + import org.chromium.chrome.browser.lifetime.ApplicationLifetime; + ++import org.chromium.chrome.browser.flags.CromiteNativeUtils; ++import org.chromium.components.webauthn.WebauthnFeatureMap; ++import org.chromium.components.webauthn.WebauthnFeatures; ++ + /** + * The "Passwords" screen in Settings, which allows the user to enable or disable password saving, + * to view saved passwords (just the username and URL), and to delete saved passwords. +@@ -322,6 +327,7 @@ public class PasswordSettings extends ChromeBaseSettingsFragment + createSavePasswordsSwitch(); + createAutoSignInCheckbox(); + createEnableAndroidAutofillSwitch(); ++ createCredManSwitch(); + + PasswordManagerHandlerProvider.getForProfile(getProfile()) + .getPasswordManagerHandler() +@@ -673,6 +679,27 @@ public class PasswordSettings extends ChromeBaseSettingsFragment + getPrefService().getBoolean(Pref.CREDENTIALS_ENABLE_AUTOSIGNIN)); + } + ++ private void createCredManSwitch() { ++ ChromeSwitchPreference credManSwitch = ++ new ChromeSwitchPreference(getStyledContext(), null); ++ credManSwitch.setTitle(R.string.enable_android_credman_title); ++ credManSwitch.setOrder(0); ++ credManSwitch.setSummary(R.string.enable_android_credman_summary); ++ credManSwitch.setChecked( ++ WebauthnFeatureMap.getInstance().isEnabled(WebauthnFeatures.WEBAUTHN_ANDROID_PASSKEY)); ++ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { ++ credManSwitch.setSummary(R.string.enable_android_credman_disabled_summary); ++ credManSwitch.setEnabled(false); ++ } ++ credManSwitch.setOnPreferenceChangeListener((preference, newValue) -> { ++ CromiteNativeUtils.setFlagEnabled("WebAuthenticationAndroidPasskey", (boolean)newValue); ++ if (!mSnackbarManagerSupplier.get().isShowing()) ++ mSnackbarManagerSupplier.get().showSnackbar(mSnackbar); ++ return true; ++ }); ++ getPreferenceScreen().addPreference(credManSwitch); ++ } ++ + private void displayManageAccountLink() { + SyncService syncService = SyncServiceFactory.getForProfile(getProfile()); + if (syncService == null || !syncService.isEngineInitialized()) { +diff --git a/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Add-setting-to-enable-Credman-for-passkeys.grdp b/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Add-setting-to-enable-Credman-for-passkeys.grdp +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Add-setting-to-enable-Credman-for-passkeys.grdp +@@ -0,0 +1,12 @@ ++ ++ ++ ++ Enable Android Credential Manager ++ ++ ++ Enable support for Android Credential Manager ++ ++ ++ Not supported on your device (require A14+) ++ ++ +diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java +--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java ++++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java +@@ -34,6 +34,8 @@ import org.chromium.blink.mojom.PublicKeyCredentialReportOptions; + import org.chromium.blink.mojom.WebAuthnClientCapability; + import org.chromium.build.annotations.NullMarked; + import org.chromium.build.annotations.Nullable; ++import org.chromium.components.webauthn.CredManSupport; ++import org.chromium.components.webauthn.cred_man.CredManSupportProvider; + import org.chromium.components.ukm.UkmRecorder; + import org.chromium.content_public.browser.RenderFrameHost; + import org.chromium.content_public.browser.WebContents; +@@ -169,8 +171,7 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon + mIsPaymentRequest = options.isPaymentCredentialCreation; + mMakeCredentialCallback = callback; + mIsOperationPending = true; +- if (!GmsCoreUtils.isWebauthnSupported() +- || (!isChrome(mWebContents) && !GmsCoreUtils.isResultReceiverSupported())) { ++ if (CredManSupportProvider.getCredManSupport() == CredManSupport.DISABLED) { + recordOutcomeEvent(MakeCredentialOutcome.OTHER_FAILURE); + onError(AuthenticatorStatus.NOT_IMPLEMENTED); + return; +@@ -234,8 +235,7 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon + mIsConditionalRequest = options.mediation == Mediation.CONDITIONAL; + mIsImmediateRequest = options.mediation == Mediation.IMMEDIATE; + +- if (!GmsCoreUtils.isWebauthnSupported() +- || (!isChrome(mWebContents) && !GmsCoreUtils.isResultReceiverSupported()) ++ if (CredManSupportProvider.getCredManSupport() == CredManSupport.DISABLED + || options.publicKey == null) { + recordOutcomeEvent(GetAssertionOutcome.OTHER_FAILURE); + onError(AuthenticatorStatus.NOT_IMPLEMENTED); +@@ -260,12 +260,11 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon + } + + private boolean couldSupportConditionalMediation() { +- return GmsCoreUtils.isWebauthnSupported() && isChrome(mWebContents); ++ return isChrome(mWebContents); + } + + private boolean couldSupportUvpaa() { +- return GmsCoreUtils.isWebauthnSupported() +- && (isChrome(mWebContents) || GmsCoreUtils.isResultReceiverSupported()); ++ return isChrome(mWebContents); + } + + @Override +@@ -329,26 +328,20 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon + capabilities.add( + createWebAuthnClientCapability( + AuthenticatorConstants.CAPABILITY_CONDITIONAL_GET, +- couldSupportConditionalMediation() && isUvpaa)); ++ true)); + capabilities.add( + createWebAuthnClientCapability( + AuthenticatorConstants.CAPABILITY_UVPAA, +- couldSupportUvpaa() && isUvpaa)); +- boolean conditionalCreateEnabled = +- couldSupportConditionalMediation() +- && DeviceFeatureMap.isEnabled( +- DeviceFeatureList.WEBAUTHN_PASSKEY_UPGRADE); ++ true)); + capabilities.add( + createWebAuthnClientCapability( + AuthenticatorConstants.CAPABILITY_CONDITIONAL_CREATE, +- isUvpaa && conditionalCreateEnabled)); ++ DeviceFeatureMap.isEnabled( ++ DeviceFeatureList.WEBAUTHN_PASSKEY_UPGRADE))); + capabilities.add( + createWebAuthnClientCapability( + AuthenticatorConstants.CAPABILITY_IMMEDIATE_GET, +- DeviceFeatureMap.isEnabled( +- DeviceFeatureList +- .WEBAUTHN_IMMEDIATE_GET) +- && isUvpaa)); ++ false)); + callback.call(capabilities.toArray(new WebAuthnClientCapability[0])); + }); + } +diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnFeatures.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnFeatures.java +--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnFeatures.java ++++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnFeatures.java +@@ -16,4 +16,6 @@ import org.chromium.build.annotations.NullMarked; + public abstract class WebauthnFeatures { + public static final String WEBAUTHN_ANDROID_PASSKEY_CACHE_MIGRATION = + "WebAuthenticationAndroidPasskeyCacheMigration"; ++ public static final String WEBAUTHN_ANDROID_PASSKEY = ++ "WebAuthenticationAndroidPasskey"; + } +diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManSupportProvider.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManSupportProvider.java +--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManSupportProvider.java ++++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManSupportProvider.java +@@ -19,9 +19,11 @@ import org.chromium.base.version_info.VersionInfo; + import org.chromium.build.annotations.NullMarked; + import org.chromium.build.annotations.Nullable; + import org.chromium.components.webauthn.CredManSupport; +-import org.chromium.components.webauthn.GmsCoreUtils; + import org.chromium.components.webauthn.WebauthnMode; + import org.chromium.components.webauthn.WebauthnModeProvider; ++import org.chromium.components.webauthn.WebauthnFeatureMap; ++import org.chromium.components.webauthn.WebauthnFeatures; ++import org.chromium.components.webauthn.WebauthnLogger; + + @NullMarked + public class CredManSupportProvider { +@@ -57,12 +59,12 @@ public class CredManSupportProvider { + } + if (getAndroidVersion() < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + sCredManSupport = CredManSupport.DISABLED; +- log(TAG, "Disabled because of Android version."); ++ WebauthnLogger.logError(TAG, "Disabled because of Android version."); + return sCredManSupport; + } +- if (notSkippedBecauseInTests() && hasOldGmsVersion()) { ++ if (!WebauthnFeatureMap.getInstance().isEnabled(WebauthnFeatures.WEBAUTHN_ANDROID_PASSKEY)) { + sCredManSupport = CredManSupport.DISABLED; +- log(TAG, "Disabled because of old GMS version."); ++ WebauthnLogger.logError(TAG, "Disabled because of user."); + return sCredManSupport; + } + if (notSkippedBecauseInTests() +@@ -70,14 +72,14 @@ public class CredManSupportProvider { + == null) { + sCredManSupport = CredManSupport.DISABLED; + recordCredManAvailability(/*available*/ false); +- log(TAG, "Disabled because CredentialManager is not available."); ++ WebauthnLogger.logError(TAG, "Disabled because CredentialManager is not available."); + return sCredManSupport; + } + recordCredManAvailability(/*available*/ true); + + final CredManUiRecommender recommender = + ServiceLoaderUtil.maybeCreate(CredManUiRecommender.class); +- boolean customUiRecommended = recommender != null && recommender.recommendsCustomUi(); ++ boolean customUiRecommended = true; + boolean gpmInCredMan = + sOverrideForcesGpm != null ? sOverrideForcesGpm : customUiRecommended; + boolean isChrome3pPwmMode = +@@ -116,13 +118,6 @@ public class CredManSupportProvider { + "WebAuthentication.Android.CredManAvailability", available); + } + +- private static boolean hasOldGmsVersion() { +- assert sOverrideAndroidVersion == null : "Don't use in testing!"; +- // The check works for unavailable and low GMS versions. `getGmsCoreVersion()` is -1 if the +- // GMS version can't be retrieved. Chrome assumes an insufficient GMS availability then. +- return GmsCoreUtils.getGmsCoreVersion() < getMinGmsVersionForCurrentChannel(); +- } +- + private static int getAndroidVersion() { + return sOverrideAndroidVersion == null ? Build.VERSION.SDK_INT : sOverrideAndroidVersion; + } +@@ -130,10 +125,4 @@ public class CredManSupportProvider { + private static boolean notSkippedBecauseInTests() { + return sOverrideForcesGpm == null && sOverrideAndroidVersion == null; + } +- +- private static int getMinGmsVersionForCurrentChannel() { +- return (VersionInfo.isBetaBuild() || VersionInfo.isStableBuild()) +- ? GMSCORE_MIN_VERSION_BETA_STABLE +- : GMSCORE_MIN_VERSION_CANARY_DEV; +- } + } +diff --git a/components/webauthn/android/webauthn_feature_map.cc b/components/webauthn/android/webauthn_feature_map.cc +--- a/components/webauthn/android/webauthn_feature_map.cc ++++ b/components/webauthn/android/webauthn_feature_map.cc +@@ -16,6 +16,7 @@ namespace { + // Array of features exposed through the Java WebauthnFeatureMap API. + const base::Feature* const kFeaturesExposedToJava[] = { + &kWebAuthnAndroidPasskeyCacheMigration, ++ &kWebAuthnAndroidPasskey, + }; + + // static +diff --git a/components/webauthn/features.cc b/components/webauthn/features.cc +--- a/components/webauthn/features.cc ++++ b/components/webauthn/features.cc +@@ -16,6 +16,10 @@ BASE_FEATURE(kWebAuthnAndroidPasskeyCacheMigration, + "WebAuthenticationAndroidPasskeyCacheMigration", + base::FEATURE_DISABLED_BY_DEFAULT); + ++CROMITE_FEATURE(kWebAuthnAndroidPasskey, ++ "WebAuthenticationAndroidPasskey", ++ base::FEATURE_DISABLED_BY_DEFAULT); ++ + #endif // BUILDFLAG(IS_ANDROID) + + } // namespace webauthn::features +diff --git a/components/webauthn/features.h b/components/webauthn/features.h +--- a/components/webauthn/features.h ++++ b/components/webauthn/features.h +@@ -20,6 +20,9 @@ namespace webauthn::features { + COMPONENT_EXPORT(WEBAUTHN) + BASE_DECLARE_FEATURE(kWebAuthnAndroidPasskeyCacheMigration); + ++COMPONENT_EXPORT(WEBAUTHN) ++BASE_DECLARE_FEATURE(kWebAuthnAndroidPasskey); ++ + #endif // BUILDFLAG(IS_ANDROID) + + } // namespace webauthn::features +diff --git a/device/fido/features.cc b/device/fido/features.cc +--- a/device/fido/features.cc ++++ b/device/fido/features.cc +@@ -163,6 +163,7 @@ BASE_FEATURE(kWebAuthnImmediateGet, + #else + base::FEATURE_ENABLED_BY_DEFAULT); + #endif // BUILDFLAG(IS_ANDROID) ++SET_CROMITE_FEATURE_DISABLED(kWebAuthnImmediateGet); + + BASE_FEATURE_PARAM(int, + kWebAuthnImmediateMediationTimeoutMilliseconds, +@@ -175,6 +176,7 @@ BASE_FEATURE_PARAM(int, + BASE_FEATURE(kWebAuthnImmediateGetAutoselect, + "WebAuthenticationImmediateGetAutoselect", + base::FEATURE_ENABLED_BY_DEFAULT); ++SET_CROMITE_FEATURE_DISABLED(kWebAuthnImmediateGetAutoselect); + + #if BUILDFLAG(IS_MAC) + // Default enabled in M139. Remove in or after M142. +diff --git a/third_party/blink/renderer/modules/credentialmanagement/authentication_credentials_container.cc b/third_party/blink/renderer/modules/credentialmanagement/authentication_credentials_container.cc +--- a/third_party/blink/renderer/modules/credentialmanagement/authentication_credentials_container.cc ++++ b/third_party/blink/renderer/modules/credentialmanagement/authentication_credentials_container.cc +@@ -1057,6 +1057,11 @@ DOMException* AuthenticatorStatusToDOMException( + const WebAuthnDOMExceptionDetailsPtr& dom_exception_details) { + DCHECK_EQ(status != AuthenticatorStatus::ERROR_WITH_DOM_EXCEPTION_DETAILS, + dom_exception_details.is_null()); ++ if (status == AuthenticatorStatus::UNKNOWN_ERROR || ++ status == AuthenticatorStatus::NOT_IMPLEMENTED) { ++ status = AuthenticatorStatus::NOT_ALLOWED_ERROR; ++ base::PlatformThread::Sleep(base::Milliseconds(base::RandInt(1000, 6000))); ++ } + switch (status) { + case AuthenticatorStatus::SUCCESS: + NOTREACHED(); +-- diff --git a/build/patches/Remove-binary-blob-integrations.patch b/build/patches/Remove-binary-blob-integrations.patch index 645b3b68..9d9a079f 100644 --- a/build/patches/Remove-binary-blob-integrations.patch +++ b/build/patches/Remove-binary-blob-integrations.patch @@ -90,14 +90,13 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html components/omnibox/common/BUILD.gn | 1 - .../components/omnibox/OmniboxFeatures.java | 8 - components/signin/public/android/BUILD.gn | 3 - - components/webauthn/android/BUILD.gn | 18 +- - .../webauthn/AuthenticatorImpl.java | 97 +-- + components/webauthn/android/BUILD.gn | 12 +- .../webauthn/ConditionalUiState.java | 15 + + .../webauthn/Fido2CredentialRequest.java | 107 +-- .../webauthn/GmsCoreGetCredentialsHelper.java | 78 +- .../components/webauthn/GmsCoreUtils.java | 31 +- .../webauthn/IdentityCredentialsHelper.java | 78 +- .../webauthn/WebauthnModeProvider.java | 15 - - .../webauthn/cred_man/CredManHelper.java | 24 +- .../cred_man/CredManMetricsHelper.java | 17 +- .../push_messaging/push_messaging_manager.cc | 2 +- content/public/android/BUILD.gn | 4 - @@ -112,7 +111,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../gms/ChromiumPlayServicesAvailability.java | 10 +- third_party/androidx/customizations.gni | 14 +- third_party/cardboard/BUILD.gn | 4 - - 88 files changed, 137 insertions(+), 2618 deletions(-) + 87 files changed, 123 insertions(+), 2612 deletions(-) create mode 100644 components/webauthn/android/java/src/org/chromium/components/webauthn/ConditionalUiState.java diff --git a/android_webview/expectations/system_webview_bundle.AndroidManifest.expected b/android_webview/expectations/system_webview_bundle.AndroidManifest.expected @@ -3254,27 +3253,16 @@ diff --git a/components/signin/public/android/BUILD.gn b/components/signin/publi diff --git a/components/webauthn/android/BUILD.gn b/components/webauthn/android/BUILD.gn --- a/components/webauthn/android/BUILD.gn +++ b/components/webauthn/android/BUILD.gn -@@ -7,8 +7,6 @@ import("//third_party/jni_zero/jni_zero.gni") - - generate_jni("jni_headers") { - sources = [ -- "java/src/org/chromium/components/webauthn/Fido2Api.java", -- "java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java", - "java/src/org/chromium/components/webauthn/InternalAuthenticator.java", - "java/src/org/chromium/components/webauthn/WebauthnBrowserBridge.java", - "java/src/org/chromium/components/webauthn/WebauthnFeatureMap.java", -@@ -40,10 +38,6 @@ android_library("java") { - "java/src/org/chromium/components/webauthn/AuthenticatorImpl.java", +@@ -41,8 +41,6 @@ android_library("java") { "java/src/org/chromium/components/webauthn/Barrier.java", "java/src/org/chromium/components/webauthn/CreateConfirmationUiDelegate.java", -- "java/src/org/chromium/components/webauthn/Fido2Api.java", + "java/src/org/chromium/components/webauthn/Fido2Api.java", - "java/src/org/chromium/components/webauthn/Fido2ApiCall.java", - "java/src/org/chromium/components/webauthn/Fido2ApiCallHelper.java", -- "java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java", + "java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java", "java/src/org/chromium/components/webauthn/FidoIntentSender.java", "java/src/org/chromium/components/webauthn/GetCredentialResponseCallback.java", - "java/src/org/chromium/components/webauthn/GetMatchingCredentialIdsResponseCallback.java", -@@ -73,11 +67,11 @@ android_library("java") { +@@ -73,11 +71,11 @@ android_library("java") { "java/src/org/chromium/components/webauthn/cred_man/GpmCredManRequestDecorator.java", ] @@ -3290,16 +3278,7 @@ diff --git a/components/webauthn/android/BUILD.gn b/components/webauthn/android/ "//base:base_java", "//base:service_loader_java", "//base/version_info/android:version_constants_java", -@@ -148,8 +142,6 @@ android_library("test_support_java") { - source_set("android") { - sources = [ - "cred_man_support.h", -- "fido2api_native_android.cc", -- "fido2credentialrequest_native_android.cc", - "internal_authenticator_android.cc", - "internal_authenticator_android.h", - "webauthn_browser_bridge.cc", -@@ -223,8 +215,6 @@ robolectric_library("junit") { +@@ -223,8 +221,6 @@ robolectric_library("junit") { deps = [ ":java", ":test_support_java", @@ -3308,193 +3287,6 @@ diff --git a/components/webauthn/android/BUILD.gn b/components/webauthn/android/ "//base:base_java", "//base:base_java_test_support", "//base:base_junit_test_support", -diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java ---- a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java -+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java -@@ -76,19 +76,11 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon - - private @Nullable MakeCredential_Response mMakeCredentialCallback; - private @Nullable GetCredential_Response mGetCredentialCallback; -- private @Nullable Fido2CredentialRequest mPendingFido2CredentialRequest; -- private final Set mUnclosedFido2CredentialRequests = new HashSet<>(); -- - // Information about the request cached here for metric reporting purposes. - private boolean mIsConditionalRequest; - private boolean mIsPaymentRequest; - private boolean mIsImmediateRequest; - -- // StaticFieldLeak complains that this is a memory leak because -- // `Fido2CredentialRequest` contains a `Context`. But this field is only -- // used in tests so a memory leak is irrelevent. -- @SuppressLint("StaticFieldLeak") -- private static @Nullable Fido2CredentialRequest sFido2CredentialRequestOverrideForTesting; - - /** - * Builds the Authenticator service implementation. -@@ -124,19 +116,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon - mCreateConfirmationUiDelegate = createConfirmationUiDelegate; - } - -- public static void overrideFido2CredentialRequestForTesting(Fido2CredentialRequest request) { -- sFido2CredentialRequestOverrideForTesting = request; -- } -- -- private Fido2CredentialRequest getFido2CredentialRequest() { -- if (sFido2CredentialRequestOverrideForTesting != null) { -- return sFido2CredentialRequestOverrideForTesting; -- } -- Fido2CredentialRequest request = new Fido2CredentialRequest(this); -- mUnclosedFido2CredentialRequests.add(request); -- return request; -- } -- - /** - * Called by InternalAuthenticatorAndroid, which facilitates WebAuthn for processes that - * originate from the browser process. Since the request is from the browser process, the -@@ -196,16 +175,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon - - private void continueMakeCredential(PublicKeyCredentialCreationOptions options) { - log(TAG, "continueMakeCredential"); -- mPendingFido2CredentialRequest = getFido2CredentialRequest(); -- mPendingFido2CredentialRequest.handleMakeCredentialRequest( -- options, -- maybeCreateBrowserOptions(), -- assertNonNull(mOrigin), -- mTopOrigin, -- mPayment, -- this::onRegisterResponse, -- this::onError, -- this::recordOutcomeEvent); - } - - private @Nullable Bundle maybeCreateBrowserOptions() { -@@ -241,17 +210,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon - onError(AuthenticatorStatus.NOT_IMPLEMENTED); - return; - } -- assumeNonNull(options.publicKey); -- -- mPendingFido2CredentialRequest = getFido2CredentialRequest(); -- mPendingFido2CredentialRequest.handleGetCredentialRequest( -- options, -- assertNonNull(mOrigin), -- mTopOrigin, -- mPayment, -- this::onCredentialResponse, -- this::onError, -- this::recordOutcomeEvent); - } - - @Override -@@ -283,10 +241,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon - decoratedCallback.call(false); - return; - } -- -- getFido2CredentialRequest() -- .handleIsUserVerifyingPlatformAuthenticatorAvailableRequest( -- isUvpaa -> decoratedCallback.call(isUvpaa)); - } - - @Override -@@ -323,34 +277,7 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon - return; - } - -- getFido2CredentialRequest() -- .handleIsUserVerifyingPlatformAuthenticatorAvailableRequest( -- isUvpaa -> { -- capabilities.add( -- createWebAuthnClientCapability( -- AuthenticatorConstants.CAPABILITY_CONDITIONAL_GET, -- couldSupportConditionalMediation() && isUvpaa)); -- capabilities.add( -- createWebAuthnClientCapability( -- AuthenticatorConstants.CAPABILITY_UVPAA, -- couldSupportUvpaa() && isUvpaa)); -- boolean conditionalCreateEnabled = -- couldSupportConditionalMediation() -- && DeviceFeatureMap.isEnabled( -- DeviceFeatureList.WEBAUTHN_PASSKEY_UPGRADE); -- capabilities.add( -- createWebAuthnClientCapability( -- AuthenticatorConstants.CAPABILITY_CONDITIONAL_CREATE, -- isUvpaa && conditionalCreateEnabled)); -- capabilities.add( -- createWebAuthnClientCapability( -- AuthenticatorConstants.CAPABILITY_IMMEDIATE_GET, -- DeviceFeatureMap.isEnabled( -- DeviceFeatureList -- .WEBAUTHN_IMMEDIATE_GET) -- && isUvpaa)); -- callback.call(capabilities.toArray(new WebAuthnClientCapability[0])); -- }); -+ callback.call(capabilities.toArray(new WebAuthnClientCapability[0])); - } - - // Helper function to create WebAuthnClientCapability instances -@@ -381,14 +308,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon - callback.onResponse(new ArrayList()); - return; - } -- -- getFido2CredentialRequest() -- .handleGetMatchingCredentialIdsRequest( -- relyingPartyId, -- credentialIds, -- requireThirdPartyPayment, -- callback, -- this::onError); - } - - @Override -@@ -399,13 +318,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon - callback.call(false); - return; - } -- -- // If the gmscore and chromium versions are out of sync for some reason, this method will -- // return true but chrome will ignore conditional requests. Android surfaces only platform -- // credentials on conditional requests, use IsUVPAA as a proxy for availability. -- getFido2CredentialRequest() -- .handleIsUserVerifyingPlatformAuthenticatorAvailableRequest( -- isUvpaa -> callback.call(isUvpaa)); - } - - @Override -@@ -417,9 +329,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon - if (!mIsOperationPending || mGetCredentialCallback == null) { - return; - } -- -- assumeNonNull(mPendingFido2CredentialRequest); -- mPendingFido2CredentialRequest.cancelGetAssertion(); - } - - /** Callbacks for receiving responses from the internal handlers. */ -@@ -468,7 +377,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon - } else if (mGetCredentialCallback != null) { - mGetCredentialCallback.call(getCredentialResponseForAssertion(status, null)); - } -- if (mPendingFido2CredentialRequest != null) mPendingFido2CredentialRequest.destroyBridge(); - cleanupRequest(); - } - -@@ -508,14 +416,11 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon - mIsOperationPending = false; - mMakeCredentialCallback = null; - mGetCredentialCallback = null; -- mPendingFido2CredentialRequest = null; - } - - @Override - public void close() { - log(TAG, "close"); -- mUnclosedFido2CredentialRequests.forEach(Fido2CredentialRequest::destroyBridge); -- mUnclosedFido2CredentialRequests.clear(); - cleanupRequest(); - } - diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/ConditionalUiState.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/ConditionalUiState.java new file mode 100644 --- /dev/null @@ -3515,6 +3307,172 @@ new file mode 100644 + CANCEL_PENDING, + CANCEL_PENDING_RP_ID_VALIDATION_COMPLETE, +} +diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java +--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java ++++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java +@@ -26,8 +26,6 @@ import android.util.Pair; + import androidx.annotation.RequiresApi; + import androidx.annotation.VisibleForTesting; + +-import com.google.android.gms.tasks.Task; +- + import org.jni_zero.JNINamespace; + import org.jni_zero.NativeMethods; + +@@ -48,7 +46,6 @@ import org.chromium.blink.mojom.ResidentKeyRequirement; + import org.chromium.blink_public.common.BlinkFeatures; + import org.chromium.build.annotations.NullMarked; + import org.chromium.build.annotations.Nullable; +-import org.chromium.components.webauthn.Fido2ApiCall.Fido2ApiCallParams; + import org.chromium.components.webauthn.cred_man.CredManHelper; + import org.chromium.components.webauthn.cred_man.CredManSupportProvider; + import org.chromium.content_public.browser.ClientDataJson; +@@ -132,12 +129,7 @@ public class Fido2CredentialRequest + */ + public Fido2CredentialRequest(AuthenticationContextProvider authenticationContextProvider) { + mAuthenticationContextProvider = authenticationContextProvider; +- boolean playServicesAvailable; +- try { +- playServicesAvailable = Fido2ApiCallHelper.getInstance().arePlayServicesAvailable(); +- } catch (Exception e) { +- playServicesAvailable = false; +- } ++ boolean playServicesAvailable = false; + mPlayServicesAvailable = playServicesAvailable; + mCredManHelper = + new CredManHelper(mAuthenticationContextProvider, this, mPlayServicesAvailable); +@@ -334,22 +326,6 @@ public class Fido2CredentialRequest + returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR); + return; + } +- try { +- Fido2ApiCallHelper.getInstance() +- .invokeFido2MakeCredential( +- mAuthenticationContextProvider, +- options, +- Uri.parse(convertOriginToString(origin)), +- clientDataHash, +- maybeBrowserOptions, +- getMaybeResultReceiver(), +- this::onGotPendingIntent, +- this::onBinderCallException); +- } catch (NoSuchAlgorithmException e) { +- mMakeCredentialErrorOutcome = MakeCredentialOutcome.ALGORITHM_NOT_SUPPORTED; +- returnErrorAndResetCallback(AuthenticatorStatus.ALGORITHM_UNSUPPORTED); +- return; +- } + return; + } + int result = +@@ -397,23 +373,6 @@ public class Fido2CredentialRequest + returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR); + return; + } +- +- try { +- Fido2ApiCallHelper.getInstance() +- .invokeFido2MakeCredential( +- mAuthenticationContextProvider, +- options, +- Uri.parse(convertOriginToString(origin)), +- clientDataHash, +- maybeBrowserOptions, +- getMaybeResultReceiver(), +- this::onGotPendingIntent, +- this::onBinderCallException); +- } catch (NoSuchAlgorithmException e) { +- mMakeCredentialErrorOutcome = MakeCredentialOutcome.ALGORITHM_NOT_SUPPORTED; +- returnErrorAndResetCallback(AuthenticatorStatus.ALGORITHM_UNSUPPORTED); +- return; +- } + } + + /** +@@ -785,32 +744,6 @@ public class Fido2CredentialRequest + callback.onIsUserVerifyingPlatformAuthenticatorAvailableResponse(false); + return; + } +- +- Fido2ApiCallParams params = +- WebauthnModeProvider.getInstance() +- .getFido2ApiCallParams(mAuthenticationContextProvider.getWebContents()); +- assertNonNull(mAuthenticationContextProvider.getContext()); +- assertNonNull(params); +- Fido2ApiCall call = new Fido2ApiCall(mAuthenticationContextProvider.getContext(), params); +- Fido2ApiCall.BooleanResult result = new Fido2ApiCall.BooleanResult(); +- Parcel args = call.start(); +- args.writeStrongBinder(result); +- +- Task task = +- call.run( +- params.mIsUserVerifyingPlatformAuthenticatorAvailableMethodId, +- Fido2ApiCall.TRANSACTION_ISUVPAA, +- args, +- result); +- task.addOnSuccessListener( +- (isUvpaa) -> { +- callback.onIsUserVerifyingPlatformAuthenticatorAvailableResponse(isUvpaa); +- }); +- task.addOnFailureListener( +- (e) -> { +- logError(TAG, "FIDO2 API call failed", e); +- callback.onIsUserVerifyingPlatformAuthenticatorAvailableResponse(false); +- }); + } + + public void handleGetMatchingCredentialIdsRequest( +@@ -1139,16 +1072,6 @@ public class Fido2CredentialRequest + if (options.mediation == Mediation.CONDITIONAL) { + mCancellableUiState = CancellableUiState.REQUEST_SENT_TO_PLATFORM; + } +- +- Fido2ApiCallHelper.getInstance() +- .invokeFido2GetAssertion( +- mAuthenticationContextProvider, +- publicKeyOptions, +- Uri.parse(callerOriginString), +- clientDataHash, +- getMaybeResultReceiver(), +- this::onGotPendingIntent, +- this::onBinderCallException); + } + + private void handleNonCredentialReturn(GetCredentialOptions options, Integer reason) { +@@ -1192,34 +1115,6 @@ public class Fido2CredentialRequest + return; + } + mCancellableUiState = CancellableUiState.REQUEST_SENT_TO_PLATFORM; +- +- Fido2ApiCallParams params = +- WebauthnModeProvider.getInstance() +- .getFido2ApiCallParams(mAuthenticationContextProvider.getWebContents()); +- assertNonNull(mAuthenticationContextProvider.getContext()); +- assertNonNull(params); +- Fido2ApiCall call = new Fido2ApiCall(mAuthenticationContextProvider.getContext(), params); +- Parcel args = call.start(); +- String callbackDescriptor = params.mCallbackDescriptor; +- Fido2ApiCall.PendingIntentResult result = +- new Fido2ApiCall.PendingIntentResult(callbackDescriptor); +- args.writeStrongBinder(result); +- args.writeInt(1); // This indicates that the following options are present. +- Fido2Api.appendBrowserGetAssertionOptionsToParcel( +- options, +- Uri.parse(callerOriginString), +- clientDataHash, +- /* tunnelId= */ null, +- /* resultReceiver= */ null, +- args); +- Task task = +- call.run( +- Fido2ApiCall.METHOD_BROWSER_HYBRID_SIGN, +- Fido2ApiCall.TRANSACTION_HYBRID_SIGN, +- args, +- result); +- task.addOnSuccessListener(this::onGotPendingIntent); +- task.addOnFailureListener(this::onBinderCallException); + } + + // Handles a PendingIntent from the GMSCore FIDO library. diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/GmsCoreGetCredentialsHelper.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/GmsCoreGetCredentialsHelper.java --- a/components/webauthn/android/java/src/org/chromium/components/webauthn/GmsCoreGetCredentialsHelper.java +++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/GmsCoreGetCredentialsHelper.java @@ -3813,75 +3771,6 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut public @WebauthnMode int getWebauthnMode(@Nullable WebContents webContents) { if (mGlobalMode != WebauthnMode.NONE) return mGlobalMode; return WebauthnModeProviderJni.get().getWebauthnModeForWebContents(webContents); -diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManHelper.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManHelper.java ---- a/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManHelper.java -+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManHelper.java -@@ -40,8 +40,6 @@ import org.chromium.build.annotations.NullMarked; - import org.chromium.build.annotations.Nullable; - import org.chromium.components.webauthn.AuthenticationContextProvider; - import org.chromium.components.webauthn.Barrier; --import org.chromium.components.webauthn.Fido2CredentialRequest.CancellableUiState; --import org.chromium.components.webauthn.Fido2CredentialRequestJni; - import org.chromium.components.webauthn.GetAssertionOutcome; - import org.chromium.components.webauthn.GetCredentialResponseCallback; - import org.chromium.components.webauthn.MakeCredentialOutcome; -@@ -83,6 +81,16 @@ public class CredManHelper { - private CredManMetricsHelper mMetricsHelper; - private @Nullable Runnable mNoCredentialsFallback; - -+ public enum CancellableUiState { -+ NONE, -+ WAITING_FOR_RP_ID_VALIDATION, -+ WAITING_FOR_CREDENTIAL_LIST, -+ WAITING_FOR_SELECTION, -+ REQUEST_SENT_TO_PLATFORM, -+ CANCEL_PENDING, -+ CANCEL_PENDING_RP_ID_VALIDATION_COMPLETE, -+ } -+ - // A callback that provides an AuthenticatorStatus error in the first argument, and optionally a - // metrics recording outcome in the second. - public interface ErrorCallback { -@@ -113,8 +121,7 @@ public class CredManHelper { - ErrorCallback errorCallback) { - log(TAG, "startMakeRequest"); - mClientDataJson = clientDataJson; -- final String requestAsJson = -- Fido2CredentialRequestJni.get().createOptionsToJson(options.serialize()); -+ final String requestAsJson = ""; - - OutcomeReceiver receiver = - new OutcomeReceiver<>() { -@@ -450,8 +457,7 @@ public class CredManHelper { - CRED_MAN_PREFIX - + "BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON"); - assertNonNull(json); -- byte[] responseSerialized = -- Fido2CredentialRequestJni.get().getCredentialResponseFromJson(json); -+ byte[] responseSerialized = null; - if (responseSerialized == null) { - logError( - TAG, -@@ -619,8 +625,7 @@ public class CredManHelper { - boolean requestPasswords, - boolean preferImmediatelyAvailable, - boolean ignoreGpm) { -- final String requestAsJson = -- Fido2CredentialRequestJni.get().getOptionsToJson(options.serialize()); -+ final String requestAsJson = ""; - - boolean hasAllowCredentials = - options.allowCredentials != null && options.allowCredentials.length != 0; -@@ -658,8 +663,7 @@ public class CredManHelper { - Bundle data) { - String json = data.getString(BUNDLE_KEY_REGISTRATION_RESPONSE_JSON); - assertNonNull(json); -- byte[] responseSerialized = -- Fido2CredentialRequestJni.get().makeCredentialResponseFromJson(json); -+ byte[] responseSerialized = null; - if (responseSerialized == null) { - logError(TAG, "Failed to convert response from CredMan to Mojo object: %s", json); - return null; diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManMetricsHelper.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManMetricsHelper.java --- a/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManMetricsHelper.java +++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManMetricsHelper.java