diff --git a/services/core/java/com/android/server/pm/ext/GmsCoreUtils.java b/services/core/java/com/android/server/pm/ext/GmsCoreUtils.java new file mode 100644 index 0000000000000..f6dafb722a0fe --- /dev/null +++ b/services/core/java/com/android/server/pm/ext/GmsCoreUtils.java @@ -0,0 +1,55 @@ +package com.android.server.pm.ext; + +import android.annotation.UserIdInt; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.ext.PackageId; +import android.util.Slog; + +public class GmsCoreUtils { + private static final String TAG = "GmsCoreUtils"; + + public static boolean isGmsRemoteCredentialsServiceComponent(ComponentName componentName) { + // FIDO2 is from "remote devices", so it's handed by the RemoteService + return componentName != null + && PackageId.GMS_CORE_NAME.equals(componentName.getPackageName()) + && "com.google.android.gms.auth.api.credentials.credman.service.RemoteService" + .equals(componentName.getClassName()); + } + + public static boolean shouldBypassRemoteEntryCredentialProviderRestrictions( + Context context, ComponentName remoteCredentialProvider, @UserIdInt int userId) { + if (!isGmsRemoteCredentialsServiceComponent(remoteCredentialProvider)) { + return false; + } + + // Ensure GMS is installed for the user that the credential request is for + final ApplicationInfo gmsAppInfo; + try { + gmsAppInfo = context.getPackageManager().getApplicationInfoAsUser( + remoteCredentialProvider.getPackageName(), 0, userId); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "failed to resolve " + remoteCredentialProvider, e); + return false; + } + // getApplicationInfoAsUser is @NonNull, but just mimicking upstream code from + // ProviderSession + if (gmsAppInfo != null) { + final int packageId = gmsAppInfo.ext().getPackageId(); + // ensure it's from verified GMS core + if (packageId == PackageId.GMS_CORE) { + // Note: Not checking for Manifest.permission.PROVIDE_REMOTE_CREDENTIALS + // (signature|privileged|role); it seems FIDO2 works fine without granting that + // permission + return true; + } else { + Slog.w(TAG,"bad gmsAppInfo packageId " + packageId + " for " + + remoteCredentialProvider); + } + } + + return false; + } +} diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index e18ef2b3230da..a44fca2e8db12 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -44,6 +44,8 @@ import android.util.Pair; import android.util.Slog; +import com.android.server.pm.ext.GmsCoreUtils; + import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -94,7 +96,7 @@ public static ProviderGetSession createNewSession( android.credentials.GetCredentialRequest filteredRequest = filterOptions(providerInfo.getCapabilities(), getRequestSession.mClientRequest, - providerInfo, getRequestSession.mHybridService); + providerInfo, getRequestSession.mHybridService, context, userId); if (filteredRequest != null) { Map beginGetOptionToCredentialOptionMap = new HashMap<>(); @@ -130,7 +132,7 @@ public static ProviderGetSession createNewSession( android.credentials.GetCredentialRequest filteredRequest = filterOptions(providerInfo.getCapabilities(), getRequestSession.mClientRequest, - providerInfo, getRequestSession.mHybridService); + providerInfo, getRequestSession.mHybridService, context, userId); if (filteredRequest != null) { Map beginGetOptionToCredentialOptionMap = new HashMap<>(); @@ -181,7 +183,9 @@ private static android.credentials.GetCredentialRequest filterOptions( List providerCapabilities, android.credentials.GetCredentialRequest clientRequest, CredentialProviderInfo info, - String hybridService) { + String hybridService, + Context context, + @UserIdInt int userId) { Slog.i(TAG, "Filtering request options for: " + info.getComponentName()); if (android.credentials.flags.Flags.hybridFilterOptFixEnabled()) { ComponentName hybridComponentName = ComponentName.unflattenFromString(hybridService); @@ -190,6 +194,13 @@ private static android.credentials.GetCredentialRequest filterOptions( Slog.i(TAG, "Skipping filtering of options for hybrid service"); return clientRequest; } + // Filter options are skipped on stock OS, since hybridComponentName corresponds to + // what's set in OEM config (GMS's RemoteService on stock Pixel) + if (GmsCoreUtils.shouldBypassRemoteEntryCredentialProviderRestrictions( + context, info.getComponentName(), userId)) { + Slog.i(TAG, "Skipping filtering of options for hybrid service due to GMS core"); + return clientRequest; + } Slog.w(TAG, "Could not parse hybrid service while filtering options"); } diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index 8f0ae90588144..4379ea144f0f5 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -33,6 +33,7 @@ import android.util.Slog; import com.android.server.credentials.metrics.ProviderSessionMetric; +import com.android.server.pm.ext.GmsCoreUtils; import java.util.UUID; @@ -253,6 +254,15 @@ protected R getProviderResponse() { protected boolean enforceRemoteEntryRestrictions( @Nullable ComponentName expectedRemoteEntryProviderService) { + if (GmsCoreUtils.shouldBypassRemoteEntryCredentialProviderRestrictions( + mContext, mComponentName, mUserId)) { + // Bypassing a frameworks OEM config check and the permission grant check for + // Manifest.permission.PROVIDE_REMOTE_CREDENTIALS. GMS doesn't seem to require + // Manifest.permission.PROVIDE_REMOTE_CREDENTIALS for FIDO2 (NFC and USB) to work. + Slog.w(TAG, "Remote entry accepted from GmsCoreUtils bypass"); + return true; + } + // Check if the service is the one set by the OEM. If not silently reject this entry if (!mComponentName.equals(expectedRemoteEntryProviderService)) { Slog.w(TAG, "Remote entry being dropped as it is not from the service "