Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d0c01417 authored by Becca Hughes's avatar Becca Hughes
Browse files

Enforce device policy in credential manager (framework)

Centralizes the provider list generation logic
in CredentialProviderInfo and enforce device
policy. Adds an test API that can be used by
CTS.

Bug: 261978289
Test: ondevice & cts
Change-Id: I4d738351e1e04f39a47428ada9f25516e5e31475
parent 31d0a293
Loading
Loading
Loading
Loading
+73 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
import static java.util.Objects.requireNonNull;

import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -30,6 +31,7 @@ import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
import android.content.pm.ServiceInfo;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
@@ -37,6 +39,8 @@ import android.os.RemoteException;
import android.provider.DeviceConfig;
import android.util.Log;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.concurrent.Executor;

@@ -54,6 +58,39 @@ import java.util.concurrent.Executor;
public final class CredentialManager {
    private static final String TAG = "CredentialManager";

    /** @hide */
    @IntDef(
            flag = true,
            prefix = {"PROVIDER_FILTER_"},
            value = {
                PROVIDER_FILTER_ALL_PROVIDERS,
                PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY,
                PROVIDER_FILTER_USER_PROVIDERS_ONLY,
            })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ProviderFilter {}

    /**
     * Returns both system and user credential providers.
     *
     * @hide
     */
    public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0;

    /**
     * Returns system credential providers only.
     *
     * @hide
     */
    public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1;

    /**
     * Returns user credential providers only.
     *
     * @hide
     */
    public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2;

    private final Context mContext;
    private final ICredentialManager mService;

@@ -404,6 +441,42 @@ public final class CredentialManager {
        }
    }

    /**
     * Returns the list of ServiceInfo for all discovered credential providers on this device.
     *
     * @hide
     */
    @NonNull
    @RequiresPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS)
    public List<ServiceInfo> getCredentialProviderServicesForTesting(
            @ProviderFilter int providerFilter) {
        try {
            return mService.getCredentialProviderServices(
                    mContext.getUserId(),
                    /* disableSystemAppVerificationForTests= */ true,
                    providerFilter);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns the list of ServiceInfo for all discovered credential providers on this device.
     *
     * @hide
     */
    @NonNull
    @RequiresPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS)
    public List<ServiceInfo> getCredentialProviderServices(
            int userId, @ProviderFilter int providerFilter) {
        try {
            return mService.getCredentialProviderServices(
                    userId, /* disableSystemAppVerificationForTests= */ false, providerFilter);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns whether the service is enabled.
     *
+3 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.credentials;

import java.util.List;

import android.content.pm.ServiceInfo;
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
@@ -57,5 +58,7 @@ interface ICredentialManager {
    void unregisterCredentialDescription(in UnregisterCredentialDescriptionRequest request, String callingPackage);

    boolean isEnabledCredentialProviderService(in ComponentName componentName, String callingPackage);

    List<ServiceInfo> getCredentialProviderServices(in int userId, in boolean disableSystemAppVerificationForTests, in int providerFilter);
}
+307 −68
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.admin.DevicePolicyManager;
import android.app.admin.PackagePolicy;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -29,6 +31,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.credentials.CredentialManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.RemoteException;
@@ -38,7 +41,10 @@ import android.util.Slog;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

/**
 * {@link ServiceInfo} and meta-data about a credential provider.
@@ -74,39 +80,124 @@ public final class CredentialProviderInfo {
    public CredentialProviderInfo(@NonNull Context context,
            @NonNull ComponentName serviceComponent, int userId, boolean isSystemProvider)
            throws PackageManager.NameNotFoundException {
        this(context, getServiceInfoOrThrow(serviceComponent, userId), isSystemProvider);
        this(
                context,
                getServiceInfoOrThrow(serviceComponent, userId),
                isSystemProvider,
                /* disableSystemAppVerificationForTests= */ false);
    }

    /**
     * Constructs an information instance of the credential provider.
     *
     * @param context the context object
     * @param serviceInfo the service info for the provider app. This must be retrieved from the
     *     {@code PackageManager}
     * @param isSystemProvider whether the provider is a system app or not
     * @param isSystemProvider whether the provider app is a system provider
     * @param disableSystemAppVerificationForTests whether to disable system app permission
     *     verification so that tests can install system providers
     * @throws SecurityException If provider does not require the relevant permission
     */
    public CredentialProviderInfo(@NonNull Context context,
    public CredentialProviderInfo(
            @NonNull Context context,
            @NonNull ServiceInfo serviceInfo,
            boolean isSystemProvider) {
        if (!Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE.equals(serviceInfo.permission)) {
            Log.i(TAG, "Credential Provider Service from : " + serviceInfo.packageName
                    + "does not require permission"
                    + Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE);
            throw new SecurityException("Service does not require the expected permission : "
                    + Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE);
            boolean isSystemProvider,
            boolean disableSystemAppVerificationForTests) {
        verifyProviderPermission(serviceInfo);
        if (isSystemProvider) {
            if (!isValidSystemProvider(
                    context, serviceInfo, disableSystemAppVerificationForTests)) {
                Slog.e(TAG, "Provider is not a valid system provider: " + serviceInfo);
                throw new SecurityException(
                        "Provider is not a valid system provider: " + serviceInfo);
            }
        }

        mIsSystemProvider = isSystemProvider;
        mContext = context;
        mServiceInfo = serviceInfo;
        mCapabilities = new ArrayList<>();
        mIcon = mServiceInfo.loadIcon(mContext.getPackageManager());
        mLabel = mServiceInfo.loadSafeLabel(
                mContext.getPackageManager(), 0 /* do not ellipsize */,
        mLabel =
                mServiceInfo.loadSafeLabel(
                        mContext.getPackageManager(),
                        0 /* do not ellipsize */,
                        TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
        mIsSystemProvider = isSystemProvider;
        Log.i(TAG, "mLabel is : " + mLabel + ", for: " + mServiceInfo.getComponentName()
                .flattenToString());
        Log.i(
                TAG,
                "mLabel is : "
                        + mLabel
                        + ", for: "
                        + mServiceInfo.getComponentName().flattenToString());
        populateProviderCapabilities(context, serviceInfo);
    }

    private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException {
        final String permission = Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE;
        if (permission.equals(serviceInfo.permission)) {
            return;
        }

        Slog.e(
                TAG,
                "Credential Provider Service from : "
                        + serviceInfo.packageName
                        + "does not require permission"
                        + permission);
        throw new SecurityException(
                "Service does not require the expected permission : " + permission);
    }

    private static boolean isSystemProviderWithValidPermission(
            ServiceInfo serviceInfo, Context context) {
        final String permission = Manifest.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE;
        try {
            ApplicationInfo appInfo =
                    context.getPackageManager()
                            .getApplicationInfo(
                                    serviceInfo.packageName,
                                    PackageManager.ApplicationInfoFlags.of(
                                            PackageManager.MATCH_SYSTEM_ONLY));
            if (appInfo != null
                    && context.checkPermission(permission, /* pid= */ -1, appInfo.uid)
                            == PackageManager.PERMISSION_GRANTED) {
                Slog.i(TAG, "SYS permission granted for: " + serviceInfo.packageName);
                return true;
            } else {
                Slog.i(TAG, "SYS permission failed for: " + serviceInfo.packageName);
            }
        } catch (PackageManager.NameNotFoundException e) {
            Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
        }
        return false;
    }

    private static boolean isValidSystemProvider(
            Context context,
            ServiceInfo serviceInfo,
            boolean disableSystemAppVerificationForTests) {
        boolean isValidSystemTestProvider =
                isTestSystemProvider(serviceInfo, disableSystemAppVerificationForTests);
        if (isValidSystemTestProvider) {
            return true;
        }
        return isSystemProviderWithValidPermission(serviceInfo, context);
    }

    private static boolean isTestSystemProvider(
            ServiceInfo serviceInfo, boolean disableSystemAppVerificationForTests) {
        if (!disableSystemAppVerificationForTests) {
            return false;
        }

        Bundle metadata = serviceInfo.metaData;
        if (metadata == null) {
            Slog.e(TAG, "metadata is null: " + serviceInfo);
            return false;
        }
        return metadata.getBoolean(CredentialProviderService.TEST_SYSTEM_PROVIDER_META_DATA_KEY);
    }

    private void populateProviderCapabilities(@NonNull Context context, ServiceInfo serviceInfo) {
        final PackageManager pm = context.getPackageManager();
        try {
@@ -133,7 +224,9 @@ public final class CredentialProviderInfo {
                mCapabilities.add(capability);
            }
        } catch (PackageManager.NameNotFoundException e) {
            Slog.i(TAG, e.getMessage());
            Slog.e(TAG, e.getMessage());
        } catch (Resources.NotFoundException e) {
            Slog.e(TAG, e.getMessage());
        }
    }

@@ -154,42 +247,82 @@ public final class CredentialProviderInfo {
    }

    /**
     * Returns the valid credential provider services available for the user with the
     * given {@code userId}.
     * Returns the valid credential provider services available for the user with the given {@code
     * userId}.
     */
    @NonNull
    public static List<CredentialProviderInfo> getAvailableSystemServices(
    private static List<ServiceInfo> getAvailableSystemServiceInfos(
            @NonNull Context context,
            @UserIdInt int userId) {
        final List<CredentialProviderInfo> services = new ArrayList<>();
            @UserIdInt int userId,
            boolean disableSystemAppVerificationForTests) {
        final List<ServiceInfo> services = new ArrayList<>();
        final List<ResolveInfo> resolveInfos = new ArrayList<>();

        final List<ResolveInfo> resolveInfos =
                context.getPackageManager().queryIntentServicesAsUser(
        resolveInfos.addAll(
                context.getPackageManager()
                        .queryIntentServicesAsUser(
                                new Intent(CredentialProviderService.SYSTEM_SERVICE_INTERFACE),
                                PackageManager.ResolveInfoFlags.of(PackageManager.GET_META_DATA),
                        userId);
                                userId));

        for (ResolveInfo resolveInfo : resolveInfos) {
            final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
            try {
                ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
                        serviceInfo.packageName,
                        PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY));
                if (appInfo != null
                        && context.checkPermission(
                                Manifest.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE,
                        /*pId=*/-1, appInfo.uid) == PackageManager.PERMISSION_GRANTED) {
                    services.add(new CredentialProviderInfo(context, serviceInfo,
                            /*isSystemProvider=*/true));
                PackageManager.ApplicationInfoFlags appInfoFlags =
                        disableSystemAppVerificationForTests
                                ? PackageManager.ApplicationInfoFlags.of(0)
                                : PackageManager.ApplicationInfoFlags.of(
                                        PackageManager.MATCH_SYSTEM_ONLY);
                ApplicationInfo appInfo =
                        context.getPackageManager()
                                .getApplicationInfo(serviceInfo.packageName, appInfoFlags);

                if (appInfo == null || serviceInfo == null) {
                    continue;
                }

                services.add(serviceInfo);
            } catch (SecurityException e) {
                Log.i(TAG, "Error getting info for " + serviceInfo + ": " + e);
                Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
            } catch (PackageManager.NameNotFoundException e) {
                Log.i(TAG, "Error getting info for " + serviceInfo + ": " + e);
                Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
            }
        }
        return services;
    }

    /**
     * Returns the valid credential provider services available for the user with the given {@code
     * userId}.
     */
    @NonNull
    public static List<CredentialProviderInfo> getAvailableSystemServices(
            @NonNull Context context,
            @UserIdInt int userId,
            boolean disableSystemAppVerificationForTests) {
        final List<CredentialProviderInfo> providerInfos = new ArrayList<>();
        for (ServiceInfo si :
                getAvailableSystemServiceInfos(
                        context, userId, disableSystemAppVerificationForTests)) {
            try {
                CredentialProviderInfo cpi =
                        new CredentialProviderInfo(
                                context,
                                si,
                                /* isSystemProvider= */ true,
                                disableSystemAppVerificationForTests);
                if (cpi.isSystemProvider()) {
                    providerInfos.add(cpi);
                } else {
                    Slog.e(TAG, "Non system provider was in system provider list.");
                }
            } catch (SecurityException e) {
                Slog.e(TAG, "Failed to create CredentialProviderInfo: " + e);
            }
        }
        return providerInfos;
    }

    /**
     * Returns true if the service supports the given {@code credentialType}, false otherwise.
     */
@@ -226,47 +359,153 @@ public final class CredentialProviderInfo {
        return Collections.unmodifiableList(mCapabilities);
    }

    private static @Nullable PackagePolicy getDeviceManagerPolicy(@NonNull Context context) {
        try {
            DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
            return dpm.getCredentialManagerPolicy();
        } catch (SecurityException e) {
            // If the current user is not enrolled in DPM then this can throw a security error.
            Log.e(TAG, "Failed to get device policy: " + e);
        }

        return null;
    }

    /**
     * Returns the valid credential provider services available for the user with the
     * given {@code userId}.
     * Returns the valid credential provider services available for the user with the given {@code
     * userId}.
     */
    @NonNull
    public static List<CredentialProviderInfo> getAvailableServices(@NonNull Context context,
            @UserIdInt int userId) {
        final List<CredentialProviderInfo> services = new ArrayList<>();
    public static List<CredentialProviderInfo> getCredentialProviderServices(
            @NonNull Context context,
            int userId,
            boolean disableSystemAppVerificationForTests,
            int providerFilter) {
        // Get the device policy.
        PackagePolicy pp = getDeviceManagerPolicy(context);

        // Generate the provider list.
        ProviderGenerator generator =
                new ProviderGenerator(
                        context, pp, disableSystemAppVerificationForTests, providerFilter);
        generator.addUserProviders(
                getUserProviders(context, userId, disableSystemAppVerificationForTests));
        generator.addSystemProviders(
                getAvailableSystemServices(context, userId, disableSystemAppVerificationForTests));
        return generator.getProviders();
    }

    private static class ProviderGenerator {
        private final Context mContext;
        private final PackagePolicy mPp;
        private final boolean mDisableSystemAppVerificationForTests;
        private final Map<String, CredentialProviderInfo> mServices = new HashMap();
        private final int mProviderFilter;

        ProviderGenerator(
                Context context,
                PackagePolicy pp,
                boolean disableSystemAppVerificationForTests,
                int providerFilter) {
            this.mContext = context;
            this.mPp = pp;
            this.mDisableSystemAppVerificationForTests = disableSystemAppVerificationForTests;
            this.mProviderFilter = providerFilter;
        }

        private boolean isPackageAllowed(boolean isSystemProvider, String packageName) {
            if (mPp == null) {
                return true;
            }

            if (isSystemProvider) {
                return mPp.getPolicyType() == PackagePolicy.PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM;
            }

            return mPp.isPackageAllowed(packageName, new HashSet<>());
        }

        public List<CredentialProviderInfo> getProviders() {
            return new ArrayList<>(mServices.values());
        }

        public void addUserProviders(List<CredentialProviderInfo> providers) {
            for (CredentialProviderInfo cpi : providers) {
                if (!cpi.isSystemProvider()) {
                    addProvider(cpi);
                }
            }
        }

        public void addSystemProviders(List<CredentialProviderInfo> providers) {
            for (CredentialProviderInfo cpi : providers) {
                if (cpi.isSystemProvider()) {
                    addProvider(cpi);
                }
            }
        }

        private boolean isProviderAllowedWithFilter(CredentialProviderInfo cpi) {
            if (mProviderFilter == CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS) {
                return true;
            }

            if (cpi.isSystemProvider()) {
                return mProviderFilter == CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY;
            } else {
                return mProviderFilter == CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY;
            }
        }

        private void addProvider(CredentialProviderInfo cpi) {
            final String componentNameString =
                    cpi.getServiceInfo().getComponentName().flattenToString();
            if (!isProviderAllowedWithFilter(cpi)) {
                return;
            }

            if (!isPackageAllowed(cpi.isSystemProvider(), cpi.getServiceInfo().packageName)) {
                return;
            }

            mServices.put(componentNameString, cpi);
        }
    }

    /**
     * Returns the valid credential provider services available for the user with the given {@code
     * userId}.
     */
    @NonNull
    private static List<CredentialProviderInfo> getUserProviders(
            @NonNull Context context,
            @UserIdInt int userId,
            boolean disableSystemAppVerificationForTests) {
        final List<CredentialProviderInfo> services = new ArrayList<>();
        final List<ResolveInfo> resolveInfos =
                context.getPackageManager().queryIntentServicesAsUser(
                context.getPackageManager()
                        .queryIntentServicesAsUser(
                                new Intent(CredentialProviderService.SERVICE_INTERFACE),
                                PackageManager.ResolveInfoFlags.of(PackageManager.GET_META_DATA),
                                userId);
        for (ResolveInfo resolveInfo : resolveInfos) {
            final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
            try {
                services.add(new CredentialProviderInfo(context,
                        serviceInfo, false));
                CredentialProviderInfo cpi =
                        new CredentialProviderInfo(
                                context,
                                serviceInfo,
                                /* isSystemProvider= */ false,
                                disableSystemAppVerificationForTests);
                if (!cpi.isSystemProvider()) {
                    services.add(cpi);
                }
            } catch (SecurityException e) {
                Log.w(TAG, "Error getting info for " + serviceInfo + ": " + e);
                Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
            } catch (Exception e) {
                Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
            }
        }
        return services;
    }

    /**
     * Returns the valid credential provider services available for the user, that can
     * support the given {@code credentialType}.
     */
    @NonNull
    public static List<CredentialProviderInfo> getAvailableServicesForCapability(
            @NonNull Context context, @UserIdInt int userId, @NonNull String credentialType) {
        List<CredentialProviderInfo> servicesForCapability = new ArrayList<>();
        final List<CredentialProviderInfo> services = getAvailableServices(context, userId);

        for (CredentialProviderInfo service : services) {
            if (service.hasCapability(credentialType)) {
                servicesForCapability.add(service);
            }
        }
        return servicesForCapability;
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -157,6 +157,10 @@ public abstract class CredentialProviderService extends Service {

    public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";

    /** @hide */
    public static final String TEST_SYSTEM_PROVIDER_META_DATA_KEY =
            "android.credentials.testsystemprovider";

    private Handler mHandler;

    /**
+48 −11

File changed.

Preview size limit exceeded, changes collapsed.

Loading