From 099f584f64c43a4bbb8588e4579aed4812ef1b0c Mon Sep 17 00:00:00 2001 From: Harsh Lal Date: Tue, 7 Feb 2023 00:13:44 +0000 Subject: [PATCH] Add privileged APIs with origin Permission PROVIDE_DEFAULT_ENABLED_SYSTEM_PROVIDER is used in http://cs/android-internal/frameworks/base/core/java/android/service/credentials/CredentialProviderInfo.java;l=178;rcl=de4db481486429671cc309f0586125ac308ceb23 Bug: 268097161 API-Coverage-Bug: 247549381 Test: local-deployment Change-Id: I56c4a4fa5b2a01c8c5a2231e2cc4cab34236298d --- core/api/current.txt | 3 + core/api/system-current.txt | 2 + .../credentials/CredentialManager.java | 105 +++++++++ .../credentials/ICredentialManager.aidl | 4 + .../credentials/CredentialProviderInfo.java | 3 +- core/res/AndroidManifest.xml | 13 +- .../credentials/CredentialManagerService.java | 199 ++++++++++++------ 7 files changed, 264 insertions(+), 65 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index f0bcf2ba2e2e4..0f929f865d143 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -83,6 +83,7 @@ package android { field public static final String CLEAR_APP_CACHE = "android.permission.CLEAR_APP_CACHE"; field public static final String CONFIGURE_WIFI_DISPLAY = "android.permission.CONFIGURE_WIFI_DISPLAY"; field public static final String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES"; + field public static final String CREDENTIAL_MANAGER_SET_ORIGIN = "android.permission.CREDENTIAL_MANAGER_SET_ORIGIN"; field public static final String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES"; field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES"; field public static final String DELIVER_COMPANION_MESSAGES = "android.permission.DELIVER_COMPANION_MESSAGES"; @@ -13514,7 +13515,9 @@ package android.credentials { public final class CredentialManager { method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method public void createCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); + method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public void createCredentialWithOrigin(@NonNull android.credentials.CreateCredentialRequest, @Nullable String, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method public void getCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); + method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public void getCredentialWithOrigin(@NonNull android.credentials.GetCredentialRequest, @Nullable String, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method public boolean isEnabledCredentialProviderService(@NonNull android.content.ComponentName); method public void registerCredentialDescription(@NonNull android.credentials.RegisterCredentialDescriptionRequest); method public void unregisterCredentialDescription(@NonNull android.credentials.UnregisterCredentialDescriptionRequest); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 20b3a263ffa1d..041f1480de802 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -246,6 +246,8 @@ package android { field public static final String PERFORM_IMS_SINGLE_REGISTRATION = "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"; field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION"; field public static final String POWER_SAVER = "android.permission.POWER_SAVER"; + field public static final String PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE = "android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE"; + field public static final String PROVIDE_HYBRID_CREDENTIAL_SERVICE = "android.permission.PROVIDE_HYBRID_CREDENTIAL_SERVICE"; field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE"; field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT"; field public static final String PROVISION_DEMO_DEVICE = "android.permission.PROVISION_DEMO_DEVICE"; diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java index 8b43a21c2fb72..54909aa53fc0d 100644 --- a/core/java/android/credentials/CredentialManager.java +++ b/core/java/android/credentials/CredentialManager.java @@ -16,6 +16,8 @@ package android.credentials; +import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN; + import static java.util.Objects.requireNonNull; import android.annotation.CallbackExecutor; @@ -123,6 +125,57 @@ public final class CredentialManager { } } + /** + * Launches the necessary flows to retrieve an app credential from the user, for the given + * origin. + * + *

The execution can potentially launch UI flows to collect user consent to using a + * credential, display a picker when multiple credentials exist, etc. + * + * @param request the request specifying type(s) of credentials to get from the user + * @param origin the origin of the calling app. Callers of this special API (e.g. browsers) + * can set this origin for an app different from their own, to be able to get credentials + * on behalf of that app. + * @param activity the activity used to launch any UI needed + * @param cancellationSignal an optional signal that allows for cancelling this call + * @param executor the callback will take place on this {@link Executor} + * @param callback the callback invoked when the request succeeds or fails + */ + @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN) + public void getCredentialWithOrigin( + @NonNull GetCredentialRequest request, + @Nullable String origin, + @NonNull Activity activity, + @Nullable CancellationSignal cancellationSignal, + @CallbackExecutor @NonNull Executor executor, + @NonNull OutcomeReceiver callback) { + requireNonNull(request, "request must not be null"); + requireNonNull(activity, "activity must not be null"); + requireNonNull(executor, "executor must not be null"); + requireNonNull(callback, "callback must not be null"); + + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + Log.w(TAG, "getCredential already canceled"); + return; + } + + ICancellationSignal cancelRemote = null; + try { + cancelRemote = + mService.executeGetCredentialWithOrigin( + request, + new GetCredentialTransport(activity, executor, callback), + mContext.getOpPackageName(), + origin); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + + if (cancellationSignal != null && cancelRemote != null) { + cancellationSignal.setRemote(cancelRemote); + } + } + /** * Launches the necessary flows to register an app credential for the user. * @@ -168,6 +221,58 @@ public final class CredentialManager { } } + /** + * Launches the necessary flows to register an app credential for the user. + * + *

The execution can potentially launch UI flows to collect user consent to creating or + * storing the new credential, etc. + * + * @param request the request specifying type(s) of credentials to get from the user, for the + * given origin + * @param origin the origin of the calling app. Callers of this special API (e.g. browsers) + * can set this origin for an app different from their own, to be able to get credentials + * on behalf of that app. + * @param activity the activity used to launch any UI needed + * @param cancellationSignal an optional signal that allows for cancelling this call + * @param executor the callback will take place on this {@link Executor} + * @param callback the callback invoked when the request succeeds or fails + */ + @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN) + public void createCredentialWithOrigin( + @NonNull CreateCredentialRequest request, + @Nullable String origin, + @NonNull Activity activity, + @Nullable CancellationSignal cancellationSignal, + @CallbackExecutor @NonNull Executor executor, + @NonNull + OutcomeReceiver callback) { + requireNonNull(request, "request must not be null"); + requireNonNull(activity, "activity must not be null"); + requireNonNull(executor, "executor must not be null"); + requireNonNull(callback, "callback must not be null"); + + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + Log.w(TAG, "createCredential already canceled"); + return; + } + + ICancellationSignal cancelRemote = null; + try { + cancelRemote = + mService.executeCreateCredentialWithOrigin( + request, + new CreateCredentialTransport(activity, executor, callback), + mContext.getOpPackageName(), + origin); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + + if (cancellationSignal != null && cancelRemote != null) { + cancellationSignal.setRemote(cancelRemote); + } + } + /** * Clears the current user credential state from all credential providers. * diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl index 885acd4f712b3..604e56b1fdbd0 100644 --- a/core/java/android/credentials/ICredentialManager.aidl +++ b/core/java/android/credentials/ICredentialManager.aidl @@ -40,8 +40,12 @@ interface ICredentialManager { @nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage); + @nullable ICancellationSignal executeGetCredentialWithOrigin(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage, String origin); + @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage); + @nullable ICancellationSignal executeCreateCredentialWithOrigin(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage, String origin); + @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage); @nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback); diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfo.java index 6a10a6ac891d1..ce8bd0c6af0ef 100644 --- a/core/java/android/service/credentials/CredentialProviderInfo.java +++ b/core/java/android/service/credentials/CredentialProviderInfo.java @@ -175,7 +175,8 @@ public final class CredentialProviderInfo { serviceInfo.packageName, PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY)); if (appInfo != null - && context.checkPermission(Manifest.permission.SYSTEM_CREDENTIAL_PROVIDER, + && context.checkPermission( + Manifest.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE, /*pId=*/-1, appInfo.uid) == PackageManager.PERMISSION_GRANTED) { services.add(new CredentialProviderInfo(context, serviceInfo, /*isSystemProvider=*/true)); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0b6b0a1350cb6..4bfeb16fccf8d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4036,14 +4036,19 @@ - + + + + - + diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index edffad299e0f4..79f619ce561ca 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -16,10 +16,12 @@ package com.android.server.credentials; +import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN; import static android.content.Context.CREDENTIAL_SERVICE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.content.ComponentName; @@ -316,22 +318,26 @@ public final class CredentialManagerService CredentialDescriptionRegistry.clearUserSession(user.getUserIdentifier()); } - private CallingAppInfo constructCallingAppInfo(String packageName, int userId) { + private CallingAppInfo constructCallingAppInfo( + String realPackageName, + int userId, + @Nullable String origin) { final PackageInfo packageInfo; + String actualPackageName = origin == null ? realPackageName : origin; try { packageInfo = - getContext() - .getPackageManager() - .getPackageInfoAsUser( - packageName, - PackageManager.PackageInfoFlags.of( - PackageManager.GET_SIGNING_CERTIFICATES), - userId); + getContext() + .getPackageManager() + .getPackageInfoAsUser( + actualPackageName, + 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); + return new CallingAppInfo(actualPackageName, null); } - return new CallingAppInfo(packageName, packageInfo.signingInfo); + return new CallingAppInfo(actualPackageName, packageInfo.signingInfo); } final class CredentialManagerServiceStub extends ICredentialManager.Stub { @@ -355,59 +361,97 @@ public final class CredentialManagerService callingUid, callback, request, - constructCallingAppInfo(callingPackage, userId), + constructCallingAppInfo(callingPackage, userId, null), CancellationSignal.fromTransport(cancelTransport)); + processGetCredential(request, callback, session); + return cancelTransport; + } + + public ICancellationSignal executeGetCredentialWithOrigin( + GetCredentialRequest request, + IGetCredentialCallback callback, + final String callingPackage, + final String origin) { + Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage); + ICancellationSignal cancelTransport = CancellationSignal.createTransport(); + + // Check privileged permissions + mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null); + + final int userId = UserHandle.getCallingUserId(); + final int callingUid = Binder.getCallingUid(); + enforceCallingPackage(callingPackage, callingUid); + + // New request session, scoped for this request only. + final GetRequestSession session = + new GetRequestSession( + getContext(), + userId, + callingUid, + callback, + request, + constructCallingAppInfo(callingPackage, userId, origin), + CancellationSignal.fromTransport(cancelTransport)); + + processGetCredential(request, callback, session); + return cancelTransport; + } + + private void processGetCredential( + GetCredentialRequest request, + IGetCredentialCallback callback, + GetRequestSession session) { List providerSessions; // TODO(b/268143699): temporarily disable the flag due to bug. if (false) { List optionsThatRequireActiveCredentials = request.getCredentialOptions().stream() - .filter( - getCredentialOption -> - !TextUtils.isEmpty( - getCredentialOption - .getCredentialRetrievalData() - .getString( - CredentialOption - .FLATTENED_REQUEST, - null))) - .toList(); + .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))) - .toList(); + .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)) - .collect(Collectors.toList()), - getFilteredResultFromRegistry(optionsThatRequireActiveCredentials)); + 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())); + session, + optionsThatDoNotRequireActiveCredentials.stream() + .map(CredentialOption::getType) + .collect(Collectors.toList())); Set all = new LinkedHashSet<>(); all.addAll(sessionsWithRemoteService); @@ -417,11 +461,11 @@ public final class CredentialManagerService } else { // Initiate all provider sessions providerSessions = - initiateProviderSessions( - session, - request.getCredentialOptions().stream() - .map(CredentialOption::getType) - .collect(Collectors.toList())); + initiateProviderSessions( + session, + request.getCredentialOptions().stream() + .map(CredentialOption::getType) + .collect(Collectors.toList())); } if (providerSessions.isEmpty()) { @@ -433,13 +477,11 @@ public final class CredentialManagerService Log.i( TAG, "Issue invoking onError on IGetCredentialCallback " - + "callback: " - + e.getMessage()); + + "callback: " + + e.getMessage()); } } providerSessions.forEach(ProviderSession::invokeSession); - - return cancelTransport; } @Override @@ -462,9 +504,47 @@ public final class CredentialManagerService callingUid, request, callback, - constructCallingAppInfo(callingPackage, userId), + constructCallingAppInfo(callingPackage, userId, null), CancellationSignal.fromTransport(cancelTransport)); + processCreateCredential(request, callback, session); + return cancelTransport; + } + + public ICancellationSignal executeCreateCredentialWithOrigin( + CreateCredentialRequest request, + ICreateCredentialCallback callback, + String callingPackage, + String origin) { + Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage); + ICancellationSignal cancelTransport = CancellationSignal.createTransport(); + + // Check privileged permissions + mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null); + + final int userId = UserHandle.getCallingUserId(); + final int callingUid = Binder.getCallingUid(); + enforceCallingPackage(callingPackage, callingUid); + + // New request session, scoped for this request only. + final CreateRequestSession session = + new CreateRequestSession( + getContext(), + userId, + callingUid, + request, + callback, + constructCallingAppInfo(callingPackage, userId, origin), + CancellationSignal.fromTransport(cancelTransport)); + + processCreateCredential(request, callback, session); + return cancelTransport; + } + + private void processCreateCredential( + CreateCredentialRequest request, + ICreateCredentialCallback callback, + CreateRequestSession session) { // Initiate all provider sessions List providerSessions = initiateProviderSessions(session, List.of(request.getType())); @@ -478,14 +558,13 @@ public final class CredentialManagerService Log.i( TAG, "Issue invoking onError on ICreateCredentialCallback " - + "callback: " - + e.getMessage()); + + "callback: " + + e.getMessage()); } } // Iterate over all provider sessions and invoke the request providerSessions.forEach(ProviderSession::invokeSession); - return cancelTransport; } @SuppressWarnings("GuardedBy") // ErrorProne requires listEnabledProviders @@ -630,7 +709,7 @@ public final class CredentialManagerService callingUid, callback, request, - constructCallingAppInfo(callingPackage, userId), + constructCallingAppInfo(callingPackage, userId, null), CancellationSignal.fromTransport(cancelTransport)); // Initiate all provider sessions