diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java index 1db14a3dda164..b4109db2032a5 100644 --- a/core/java/android/credentials/CredentialManager.java +++ b/core/java/android/credentials/CredentialManager.java @@ -222,7 +222,7 @@ public final class CredentialManager { * @param callback the callback invoked when the request succeeds or fails * @hide */ - @RequiresPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS) + @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void listEnabledProviders( @Nullable CancellationSignal cancellationSignal, @CallbackExecutor @NonNull Executor executor, @@ -286,7 +286,7 @@ public final class CredentialManager { */ public static boolean isServiceEnabled(Context context) { if (context == null) { - return false; + return false; } CredentialManager credentialManager = (CredentialManager) context.getSystemService(Context.CREDENTIAL_SERVICE); @@ -298,8 +298,7 @@ public final class CredentialManager { private boolean isServiceEnabled() { return DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, - true); + DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true); } /** @@ -313,22 +312,19 @@ public final class CredentialManager { } /** - * Registers a {@link CredentialDescription} for an actively provisioned {@link Credential} - * a CredentialProvider has. This registry will then be used to determine where to - * fetch the requested {@link Credential} from. Not all credential types will be supported. - * The distinction will be made by the JetPack layer. For the types that are supported, - * JetPack will add a new key-value pair into {@link GetCredentialRequest}. These will not - * be persistent on the device. The Credential Providers will need to call this API again - * upon device reboot. + * Registers a {@link CredentialDescription} for an actively provisioned {@link Credential} a + * CredentialProvider has. This registry will then be used to determine where to fetch the + * requested {@link Credential} from. Not all credential types will be supported. The + * distinction will be made by the JetPack layer. For the types that are supported, JetPack will + * add a new key-value pair into {@link GetCredentialRequest}. These will not be persistent on + * the device. The Credential Providers will need to call this API again upon device reboot. * * @param request the request data - * - * @throws {@link UnsupportedOperationException} if the feature has not been enabled. - * @throws {@link com.android.server.credentials.NonCredentialProviderCallerException} - * if the calling package name is not also listed as a Credential Provider. - * @throws {@link IllegalArgumentException} if the calling Credential Provider can not handle - * one or more of the Credential Types that are sent for registration. - * + * @throws {@link UnsupportedOperationException} if the feature has not been enabled. + * @throws {@link com.android.server.credentials.NonCredentialProviderCallerException} if the + * calling package name is not also listed as a Credential Provider. + * @throws {@link IllegalArgumentException} if the calling Credential Provider can not handle + * one or more of the Credential Types that are sent for registration. */ public void registerCredentialDescription( @NonNull RegisterCredentialDescriptionRequest request) { @@ -346,16 +342,12 @@ public final class CredentialManager { } } - /** - * Unregisters a {@link CredentialDescription} for an actively provisioned {@link Credential} + * Unregisters a {@link CredentialDescription} for an actively provisioned {@link Credential} * that has been registered previously. * - * * @param request the request data - * - * @throws {@link UnsupportedOperationException} if the feature has not been enabled. - * + * @throws {@link UnsupportedOperationException} if the feature has not been enabled. */ public void unregisterCredentialDescription( @NonNull UnregisterCredentialDescriptionRequest request) { @@ -371,7 +363,6 @@ public final class CredentialManager { } catch (RemoteException e) { e.rethrowFromSystemServer(); } - } private static class GetCredentialTransport extends IGetCredentialCallback.Stub { diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index ea63c30a911c2..6cfb26b94caa5 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -17,6 +17,7 @@ package com.android.server.credentials; import static android.content.Context.CREDENTIAL_SERVICE; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.annotation.NonNull; import android.annotation.UserIdInt; @@ -81,17 +82,17 @@ public final class CredentialManagerService private static final String TAG = "CredManSysService"; private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API = "enable_credential_description_api"; + private static final String PERMISSION_DENIED_ERROR = "permission_denied"; + private static final String PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR = + "Caller is missing WRITE_SECURE_SETTINGS permission"; private final Context mContext; - /** - * Cache of system service list per user id. - */ + /** Cache of system service list per user id. */ @GuardedBy("mLock") private final SparseArray> mSystemServicesCacheList = new SparseArray<>(); - public CredentialManagerService(@NonNull Context context) { super( context, @@ -109,9 +110,11 @@ public final class CredentialManagerService List services = new ArrayList<>(); List credentialProviderInfos = CredentialProviderInfo.getAvailableSystemServices(mContext, resolvedUserId); - credentialProviderInfos.forEach(info -> { - services.add(new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info)); - }); + credentialProviderInfos.forEach( + info -> { + services.add( + new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info)); + }); return services; } @@ -174,10 +177,9 @@ public final class CredentialManagerService CredentialManagerServiceImpl serviceToBeRemoved = null; for (CredentialManagerServiceImpl service : services) { if (service != null) { - CredentialProviderInfo credentialProviderInfo = - service.getCredentialProviderInfo(); - ComponentName componentName = credentialProviderInfo.getServiceInfo() - .getComponentName(); + CredentialProviderInfo credentialProviderInfo = service.getCredentialProviderInfo(); + ComponentName componentName = + credentialProviderInfo.getServiceInfo().getComponentName(); if (packageName.equals(componentName.getPackageName())) { serviceToBeRemoved = service; removeServiceFromMultiModeSettings(componentName.flattenToString(), userId); @@ -193,7 +195,6 @@ public final class CredentialManagerService // TODO("Iterate over system services and remove if needed") } - @GuardedBy("mLock") private List getOrConstructSystemServiceListLock( int resolvedUserId) { @@ -205,6 +206,16 @@ public final class CredentialManagerService return services; } + private boolean hasWriteSecureSettingsPermission() { + final String permission = android.Manifest.permission.WRITE_SECURE_SETTINGS; + final boolean result = + mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED; + if (!result) { + Slog.e(TAG, "Caller does not have WRITE_SECURE_SETTINGS permission."); + } + return result; + } + private void runForUser(@NonNull final Consumer c) { final int userId = UserHandle.getCallingUserId(); final long origId = Binder.clearCallingIdentity(); @@ -222,8 +233,7 @@ public final class CredentialManagerService } @GuardedBy("mLock") - private List getAllCredentialProviderServicesLocked( - int userId) { + private List getAllCredentialProviderServicesLocked(int userId) { List concatenatedServices = new ArrayList<>(); List userConfigurableServices = getServiceListForUserLocked(userId); @@ -243,16 +253,18 @@ public final class CredentialManagerService // to be guarded by 'service.mLock', which is the same as mLock. private List initiateProviderSessionsWithActiveContainers( GetRequestSession session, - List requestOptions, Set activeCredentialContainers) { + List requestOptions, + Set activeCredentialContainers) { List providerSessions = new ArrayList<>(); // Invoke all services of a user to initiate a provider session - for (String packageName: activeCredentialContainers) { - providerSessions.add(ProviderRegistryGetSession.createNewSession( - mContext, - UserHandle.getCallingUserId(), - session, - packageName, - requestOptions)); + for (String packageName : activeCredentialContainers) { + providerSessions.add( + ProviderRegistryGetSession.createNewSession( + mContext, + UserHandle.getCallingUserId(), + session, + packageName, + requestOptions)); } return providerSessions; } @@ -260,16 +272,17 @@ public final class CredentialManagerService @NonNull private Set getFilteredResultFromRegistry(List options) { // Session for active/provisioned credential descriptions; - CredentialDescriptionRegistry registry = CredentialDescriptionRegistry - .forUser(UserHandle.getCallingUserId()); + CredentialDescriptionRegistry registry = + CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId()); // All requested credential descriptions based on the given request. Set requestedCredentialDescriptions = - options.stream().map( - getCredentialOption -> getCredentialOption - .getCredentialRetrievalData() - .getString(CredentialOption - .FLATTENED_REQUEST)) + options.stream() + .map( + getCredentialOption -> + getCredentialOption + .getCredentialRetrievalData() + .getString(CredentialOption.FLATTENED_REQUEST)) .collect(Collectors.toSet()); // All requested credential descriptions based on the given request. @@ -306,10 +319,14 @@ public final class CredentialManagerService private CallingAppInfo constructCallingAppInfo(String packageName, int userId) { final PackageInfo packageInfo; try { - packageInfo = getContext().getPackageManager().getPackageInfoAsUser( - packageName, - PackageManager.PackageInfoFlags.of(PackageManager.GET_SIGNING_CERTIFICATES), - userId); + packageInfo = + getContext() + .getPackageManager() + .getPackageInfoAsUser( + packageName, + PackageManager.PackageInfoFlags.of( + PackageManager.GET_SIGNING_CERTIFICATES), + userId); } catch (PackageManager.NameNotFoundException e) { Log.i(TAG, "Issue while retrieving signatureInfo : " + e.getMessage()); return new CallingAppInfo(packageName, null); @@ -345,37 +362,50 @@ public final class CredentialManagerService if (isCredentialDescriptionApiEnabled()) { List optionsThatRequireActiveCredentials = request.getCredentialOptions().stream() - .filter(getCredentialOption -> - !TextUtils.isEmpty(getCredentialOption - .getCredentialRetrievalData().getString( - CredentialOption - .FLATTENED_REQUEST, null))) + .filter( + getCredentialOption -> + !TextUtils.isEmpty( + getCredentialOption + .getCredentialRetrievalData() + .getString( + CredentialOption + .FLATTENED_REQUEST, + null))) .toList(); List optionsThatDoNotRequireActiveCredentials = request.getCredentialOptions().stream() - .filter(getCredentialOption -> - TextUtils.isEmpty(getCredentialOption - .getCredentialRetrievalData().getString( - CredentialOption - .FLATTENED_REQUEST, null))) + .filter( + getCredentialOption -> + TextUtils.isEmpty( + getCredentialOption + .getCredentialRetrievalData() + .getString( + CredentialOption + .FLATTENED_REQUEST, + null))) .toList(); List sessionsWithoutRemoteService = - initiateProviderSessionsWithActiveContainers(session, - optionsThatRequireActiveCredentials - .stream().map(getCredentialOption -> - getCredentialOption.getCredentialRetrievalData() - .getString(CredentialOption - .FLATTENED_REQUEST)) + initiateProviderSessionsWithActiveContainers( + session, + optionsThatRequireActiveCredentials.stream() + .map( + getCredentialOption -> + getCredentialOption + .getCredentialRetrievalData() + .getString( + CredentialOption + .FLATTENED_REQUEST)) .collect(Collectors.toList()), getFilteredResultFromRegistry(optionsThatRequireActiveCredentials)); - List sessionsWithRemoteService = initiateProviderSessions( - session, - optionsThatDoNotRequireActiveCredentials.stream() - .map(CredentialOption::getType) - .collect(Collectors.toList())); + List sessionsWithRemoteService = + initiateProviderSessions( + session, + optionsThatDoNotRequireActiveCredentials.stream() + .map(CredentialOption::getType) + .collect(Collectors.toList())); Set all = new LinkedHashSet<>(); all.addAll(sessionsWithRemoteService); @@ -394,7 +424,8 @@ public final class CredentialManagerService if (providerSessions.isEmpty()) { try { - callback.onError(GetCredentialException.TYPE_NO_CREDENTIAL, + callback.onError( + GetCredentialException.TYPE_NO_CREDENTIAL, "No credentials available on this device."); } catch (RemoteException e) { Log.i( @@ -437,7 +468,8 @@ public final class CredentialManagerService if (providerSessions.isEmpty()) { try { - callback.onError(CreateCredentialException.TYPE_NO_CREDENTIAL, + callback.onError( + CreateCredentialException.TYPE_NO_CREDENTIAL, "No credentials available on this device."); } catch (RemoteException e) { Log.i( @@ -460,6 +492,16 @@ public final class CredentialManagerService Log.i(TAG, "listEnabledProviders"); ICancellationSignal cancelTransport = CancellationSignal.createTransport(); + if (!hasWriteSecureSettingsPermission()) { + try { + callback.onError( + PERMISSION_DENIED_ERROR, PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR); + } catch (RemoteException e) { + Log.e(TAG, "Issue with invoking response: " + e.getMessage()); + } + return cancelTransport; + } + List enabledProviders = new ArrayList<>(); runForUser( (service) -> { @@ -482,6 +524,16 @@ public final class CredentialManagerService List providers, int userId, ISetEnabledProvidersCallback callback) { Log.i(TAG, "setEnabledProviders"); + if (!hasWriteSecureSettingsPermission()) { + try { + callback.onError( + PERMISSION_DENIED_ERROR, PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR); + } catch (RemoteException e) { + Log.e(TAG, "Issue with invoking response: " + e.getMessage()); + } + return; + } + userId = ActivityManager.handleIncomingUser( Binder.getCallingPid(), @@ -550,8 +602,7 @@ public final class CredentialManagerService if (providerSessions.isEmpty()) { try { // TODO("Replace with properly defined error type") - callback.onError("UNKNOWN", "No crdentials available on this " - + "device"); + callback.onError("UNKNOWN", "No crdentials available on this " + "device"); } catch (RemoteException e) { Log.i( TAG, @@ -569,39 +620,50 @@ public final class CredentialManagerService @Override public void registerCredentialDescription( RegisterCredentialDescriptionRequest request, String callingPackage) - throws IllegalArgumentException , NonCredentialProviderCallerException { + throws IllegalArgumentException, NonCredentialProviderCallerException { Log.i(TAG, "registerCredentialDescription"); List services = - CredentialProviderInfo.getAvailableServices(mContext, - UserHandle.getCallingUserId()); + CredentialProviderInfo.getAvailableServices( + mContext, UserHandle.getCallingUserId()); - List providers = services.stream() - .map(credentialProviderInfo - -> credentialProviderInfo.getServiceInfo().packageName).toList(); + List providers = + services.stream() + .map( + credentialProviderInfo -> + credentialProviderInfo.getServiceInfo().packageName) + .toList(); if (!providers.contains(callingPackage)) { throw new NonCredentialProviderCallerException(callingPackage); } - List matchingService = services.stream().filter( - credentialProviderInfo -> - credentialProviderInfo.getServiceInfo() - .packageName.equals(callingPackage)).toList(); + List matchingService = + services.stream() + .filter( + credentialProviderInfo -> + credentialProviderInfo + .getServiceInfo() + .packageName + .equals(callingPackage)) + .toList(); CredentialProviderInfo credentialProviderInfo = matchingService.get(0); - Set supportedTypes = request.getCredentialDescriptions() - .stream().map(CredentialDescription::getType).filter( - credentialProviderInfo::hasCapability).collect(Collectors.toSet()); + Set supportedTypes = + request.getCredentialDescriptions().stream() + .map(CredentialDescription::getType) + .filter(credentialProviderInfo::hasCapability) + .collect(Collectors.toSet()); if (supportedTypes.size() != request.getCredentialDescriptions().size()) { - throw new IllegalArgumentException("CredentialProvider does not support one or more" - + "of the registered types. Check your XML entry."); + throw new IllegalArgumentException( + "CredentialProvider does not support one or more" + + "of the registered types. Check your XML entry."); } - CredentialDescriptionRegistry session = CredentialDescriptionRegistry - .forUser(UserHandle.getCallingUserId()); + CredentialDescriptionRegistry session = + CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId()); session.executeRegisterRequest(request, callingPackage); } @@ -614,19 +676,22 @@ public final class CredentialManagerService ICancellationSignal cancelTransport = CancellationSignal.createTransport(); List services = - CredentialProviderInfo.getAvailableServices(mContext, - UserHandle.getCallingUserId()); + CredentialProviderInfo.getAvailableServices( + mContext, UserHandle.getCallingUserId()); - List providers = services.stream() - .map(credentialProviderInfo - -> credentialProviderInfo.getServiceInfo().packageName).toList(); + List providers = + services.stream() + .map( + credentialProviderInfo -> + credentialProviderInfo.getServiceInfo().packageName) + .toList(); if (!providers.contains(callingPackage)) { throw new NonCredentialProviderCallerException(callingPackage); } - CredentialDescriptionRegistry session = CredentialDescriptionRegistry - .forUser(UserHandle.getCallingUserId()); + CredentialDescriptionRegistry session = + CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId()); session.executeUnregisterRequest(request, callingPackage); }