Skip to content

Commit a2fd6a8

Browse files
committed
allow GMS remote credential service to be used
Currently, GMS allows passkeys via hardware keys ("remote credentials" from a "remote device") by its RemoteService, but this is being filtered as sandboxed Google Play doesn't result in system services. The RemoteService is also expected to be set as an OEM config value in frameworks-res (config_defaultCredentialManagerHybridService). This will make it so that external devices (e.g. FIDO2 with NFC / USB) can be used with sandboxed Google Play to register hardware keys in Vanadium / Chrome, along with improving hardware key functionality in other apps that make credential requests with android.credentials.CredentialManager (Context.CREDENTIAL_SERVICE) directly instead of contacting Google Play services. It still comes with the caveat that for some apps, Google has to be enabled as a credential service under Settings > Passwords, passkeys & accounts, as sandboxed GMS services are not system credential providers and have to be explicitly enabled as user credential providers. Apps that contact GMS directly for credentials / passkeys still work without needing to enable Google as a credential service (e.g., Vanadium / Chrome will fall back to contacting Play services directly if the framework GetCredentialRequest fails which allows authentication to work without this patch, but they don't such a fallback when creating credentials). Test: atest CtsCredentialManagerTestCases There's also FrameworksServicesTests:com.android.server.credentials, but currently it has some Mockito failures
1 parent aa34054 commit a2fd6a8

File tree

3 files changed

+79
-3
lines changed

3 files changed

+79
-3
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.android.server.pm.ext;
2+
3+
import android.annotation.UserIdInt;
4+
import android.content.ComponentName;
5+
import android.content.Context;
6+
import android.content.pm.ApplicationInfo;
7+
import android.content.pm.PackageManager;
8+
import android.ext.PackageId;
9+
import android.util.Slog;
10+
11+
public class GmsCoreUtils {
12+
private static final String TAG = "GmsCoreUtils";
13+
14+
public static boolean isGmsRemoteCredentialsServiceComponent(ComponentName componentName) {
15+
// FIDO2 is from "remote devices", so it's handed by the RemoteService
16+
return componentName != null
17+
&& PackageId.GMS_CORE_NAME.equals(componentName.getPackageName())
18+
&& "com.google.android.gms.auth.api.credentials.credman.service.RemoteService"
19+
.equals(componentName.getClassName());
20+
}
21+
22+
public static boolean shouldBypassRemoteEntryCredentialProviderRestrictions(
23+
Context context, ComponentName remoteCredentialProvider, @UserIdInt int userId) {
24+
if (!isGmsRemoteCredentialsServiceComponent(remoteCredentialProvider)) {
25+
return false;
26+
}
27+
28+
// Ensure GMS is installed for the user that the credential request is for
29+
final ApplicationInfo gmsAppInfo;
30+
try {
31+
gmsAppInfo = context.getPackageManager().getApplicationInfoAsUser(
32+
remoteCredentialProvider.getPackageName(), 0, userId);
33+
} catch (PackageManager.NameNotFoundException e) {
34+
Slog.w(TAG, "failed to resolve " + remoteCredentialProvider, e);
35+
return false;
36+
}
37+
// getApplicationInfoAsUser is @NonNull, but just mimicking upstream code from
38+
// ProviderSession
39+
if (gmsAppInfo != null) {
40+
final int packageId = gmsAppInfo.ext().getPackageId();
41+
// ensure it's from verified GMS core
42+
if (packageId == PackageId.GMS_CORE) {
43+
// Note: Not checking for Manifest.permission.PROVIDE_REMOTE_CREDENTIALS
44+
// (signature|privileged|role); it seems FIDO2 works fine without granting that
45+
// permission
46+
return true;
47+
} else {
48+
Slog.w(TAG,"bad gmsAppInfo packageId " + packageId + " for "
49+
+ remoteCredentialProvider);
50+
}
51+
}
52+
53+
return false;
54+
}
55+
}

services/credentials/java/com/android/server/credentials/ProviderGetSession.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import android.util.Pair;
4545
import android.util.Slog;
4646

47+
import com.android.server.pm.ext.GmsCoreUtils;
48+
4749
import java.util.ArrayList;
4850
import java.util.HashMap;
4951
import java.util.HashSet;
@@ -94,7 +96,7 @@ public static ProviderGetSession createNewSession(
9496
android.credentials.GetCredentialRequest filteredRequest =
9597
filterOptions(providerInfo.getCapabilities(),
9698
getRequestSession.mClientRequest,
97-
providerInfo, getRequestSession.mHybridService);
99+
providerInfo, getRequestSession.mHybridService, context, userId);
98100
if (filteredRequest != null) {
99101
Map<String, CredentialOption> beginGetOptionToCredentialOptionMap =
100102
new HashMap<>();
@@ -130,7 +132,7 @@ public static ProviderGetSession createNewSession(
130132
android.credentials.GetCredentialRequest filteredRequest =
131133
filterOptions(providerInfo.getCapabilities(),
132134
getRequestSession.mClientRequest,
133-
providerInfo, getRequestSession.mHybridService);
135+
providerInfo, getRequestSession.mHybridService, context, userId);
134136
if (filteredRequest != null) {
135137
Map<String, CredentialOption> beginGetOptionToCredentialOptionMap =
136138
new HashMap<>();
@@ -181,7 +183,9 @@ private static android.credentials.GetCredentialRequest filterOptions(
181183
List<String> providerCapabilities,
182184
android.credentials.GetCredentialRequest clientRequest,
183185
CredentialProviderInfo info,
184-
String hybridService) {
186+
String hybridService,
187+
Context context,
188+
@UserIdInt int userId) {
185189
Slog.i(TAG, "Filtering request options for: " + info.getComponentName());
186190
if (android.credentials.flags.Flags.hybridFilterOptFixEnabled()) {
187191
ComponentName hybridComponentName = ComponentName.unflattenFromString(hybridService);
@@ -190,6 +194,13 @@ private static android.credentials.GetCredentialRequest filterOptions(
190194
Slog.i(TAG, "Skipping filtering of options for hybrid service");
191195
return clientRequest;
192196
}
197+
// Filter options are skipped on stock OS, since hybridComponentName corresponds to
198+
// what's set in OEM config (GMS's RemoteService on stock Pixel)
199+
if (GmsCoreUtils.shouldBypassRemoteEntryCredentialProviderRestrictions(
200+
context, info.getComponentName(), userId)) {
201+
Slog.i(TAG, "Skipping filtering of options for hybrid service due to GMS core");
202+
return clientRequest;
203+
}
193204
Slog.w(TAG, "Could not parse hybrid service while filtering options");
194205
}
195206

services/credentials/java/com/android/server/credentials/ProviderSession.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import android.util.Slog;
3434

3535
import com.android.server.credentials.metrics.ProviderSessionMetric;
36+
import com.android.server.pm.ext.GmsCoreUtils;
3637

3738
import java.util.UUID;
3839

@@ -253,6 +254,15 @@ protected R getProviderResponse() {
253254

254255
protected boolean enforceRemoteEntryRestrictions(
255256
@Nullable ComponentName expectedRemoteEntryProviderService) {
257+
if (GmsCoreUtils.shouldBypassRemoteEntryCredentialProviderRestrictions(
258+
mContext, mComponentName, mUserId)) {
259+
// Bypassing a frameworks OEM config check and the permission grant check for
260+
// Manifest.permission.PROVIDE_REMOTE_CREDENTIALS. GMS doesn't seem to require
261+
// Manifest.permission.PROVIDE_REMOTE_CREDENTIALS for FIDO2 (NFC and USB) to work.
262+
Slog.w(TAG, "Remote entry accepted from GmsCoreUtils bypass");
263+
return true;
264+
}
265+
256266
// Check if the service is the one set by the OEM. If not silently reject this entry
257267
if (!mComponentName.equals(expectedRemoteEntryProviderService)) {
258268
Slog.w(TAG, "Remote entry being dropped as it is not from the service "

0 commit comments

Comments
 (0)