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

Commit dfdbd2e9 authored by Alex Johnston's avatar Alex Johnston
Browse files

Add credential management app access control DPM

Background
* This is part of the work to support
  a credential management app on
  unmanaged devices.

Changes
* Add isCredentialManagementApp access
  control check to the following DPM API
  methods:
   - installKeyPair
   - removeKeyPair
   - generateKeyPair
   - setKeyPairCertificate

Bug: 165641221
Test: Manual testing with TestDPC
Change-Id: Ib88f077ee1f26c08e648ab8b4bb3544b42078d57
parent e17727eb
Loading
Loading
Loading
Loading
+106 −30
Original line number Diff line number Diff line
@@ -5213,9 +5213,22 @@ public class DevicePolicyManager {
    }
    /**
     * Called by a device or profile owner, or delegated certificate installer, to install a
     * certificate and corresponding private key. All apps within the profile will be able to access
     * the certificate and use the private key, given direct user approval.
     * This API can be called by the following to install a certificate and corresponding
     * private key:
     * <ul>
     *    <li>Device owner</li>
     *    <li>Profile owner</li>
     *    <li>Delegated certificate installer</li>
     *    <li>Credential management app</li>
     * </ul>
     * All apps within the profile will be able to access the certificate and use the private key,
     * given direct user approval.
     *
     * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app
     * can call this API. However, this API sets the key pair as user selectable by default,
     * which is not permitted when called by the credential management app. Instead,
     * {@link #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int)} should be
     * called with {@link #INSTALLKEY_SET_USER_SELECTABLE} not set as a flag.
     *
     * <p>Access to the installed credentials will not be granted to the caller of this API without
     * direct user approval. This is for security - should a certificate installer become
@@ -5246,10 +5259,23 @@ public class DevicePolicyManager {
    }
    /**
     * Called by a device or profile owner, or delegated certificate installer, to install a
     * certificate chain and corresponding private key for the leaf certificate. All apps within the
     * profile will be able to access the certificate chain and use the private key, given direct
     * user approval.
     * This API can be called by the following to install a certificate chain and corresponding
     * private key for the leaf certificate:
     * <ul>
     *    <li>Device owner</li>
     *    <li>Profile owner</li>
     *    <li>Delegated certificate installer</li>
     *    <li>Credential management app</li>
     * </ul>
     * All apps within the profile will be able to access the certificate chain and use the private
     * key, given direct user approval.
     *
     * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app
     * can call this API. However, this API sets the key pair as user selectable by default,
     * which is not permitted when called by the credential management app. Instead,
     * {@link #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int)} should be
     * called with {@link #INSTALLKEY_SET_USER_SELECTABLE} not set as a flag.
     * Note, there can only be a credential management app on an unmanaged device.
     *
     * <p>The caller of this API may grant itself access to the certificate and private key
     * immediately, without user approval. It is a best practice not to request this unless strictly
@@ -5287,10 +5313,26 @@ public class DevicePolicyManager {
    }
    /**
     * Called by a device or profile owner, or delegated certificate installer, to install a
     * certificate chain and corresponding private key for the leaf certificate. All apps within the
     * profile will be able to access the certificate chain and use the private key, given direct
     * user approval (if the user is allowed to select the private key).
     * This API can be called by the following to install a certificate chain and corresponding
     * private key for the leaf certificate:
     * <ul>
     *    <li>Device owner</li>
     *    <li>Profile owner</li>
     *    <li>Delegated certificate installer</li>
     *    <li>Credential management app</li>
     * </ul>
     * All apps within the profile will be able to access the certificate chain and use the
     * private key, given direct user approval (if the user is allowed to select the private key).
     *
     * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app
     * can call this API. If called by the credential management app:
     * <ul>
     *    <li>The componentName must be {@code null}r</li>
     *    <li>The alias must exist in the credential management app's
     *    {@link android.security.AppUriAuthenticationPolicy}</li>
     *    <li>The key pair must not be user selectable</li>
     * </ul>
     * Note, there can only be a credential management app on an unmanaged device.
     *
     * <p>The caller of this API may grant itself access to the certificate and private key
     * immediately, without user approval. It is a best practice not to request this unless strictly
@@ -5316,7 +5358,8 @@ public class DevicePolicyManager {
     *        {@link #INSTALLKEY_REQUEST_CREDENTIALS_ACCESS}.
     * @return {@code true} if the keys were installed, {@code false} otherwise.
     * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
     *         owner.
     *         owner, or {@code admin} is null but the calling application is not a delegated
     *         certificate installer or credential management app.
     * @see android.security.KeyChain#getCertificateChain
     * @see #setDelegatedScopes
     * @see #DELEGATION_CERT_INSTALL
@@ -5349,15 +5392,26 @@ public class DevicePolicyManager {
    }
    /**
     * Called by a device or profile owner, or delegated certificate installer, to remove a
     * certificate and private key pair installed under a given alias.
     * This API can be called by the following to remove a certificate and private key pair
     * installed under a given alias:
     * <ul>
     *    <li>Device owner</li>
     *    <li>Profile owner</li>
     *    <li>Delegated certificate installer</li>
     *    <li>Credential management app</li>
     * </ul>
     *
     * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app
     * can call this API. If called by the credential management app, the componentName must be
     * {@code null}. Note, there can only be a credential management app on an unmanaged device.
     *
     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
     *        {@code null} if calling from a delegated certificate installer.
     * @param alias The private key alias under which the certificate is installed.
     * @return {@code true} if the private key alias no longer exists, {@code false} otherwise.
     * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
     *         owner.
     *         owner, or {@code admin} is null but the calling application is not a delegated
     *         certificate installer or credential management app.
     * @see #setDelegatedScopes
     * @see #DELEGATION_CERT_INSTALL
     */
@@ -5392,10 +5446,20 @@ public class DevicePolicyManager {
    }
    /**
     * Called by a device or profile owner, or delegated certificate installer, to generate a
     * new private/public key pair. If the device supports key generation via secure hardware,
     * this method is useful for creating a key in KeyChain that never left the secure hardware.
     * Access to the key is controlled the same way as in {@link #installKeyPair}.
     * This API can be called by the following to generate a new private/public key pair:
     * <ul>
     *    <li>Device owner</li>
     *    <li>Profile owner</li>
     *    <li>Delegated certificate installer</li>
     *    <li>Credential management app</li>
     * </ul>
     * If the device supports key generation via secure hardware, this method is useful for
     * creating a key in KeyChain that never left the secure hardware. Access to the key is
     * controlled the same way as in {@link #installKeyPair}.
     *
     * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app
     * can call this API. If called by the credential management app, the componentName must be
     * {@code null}. Note, there can only be a credential management app on an unmanaged device.
     *
     * <p>Because this method might take several seconds to complete, it should only be called from
     * a worker thread. This method returns {@code null} when called from the main thread.
@@ -5418,9 +5482,10 @@ public class DevicePolicyManager {
     * supports these features, refer to {@link #isDeviceIdAttestationSupported()} and
     * {@link #isUniqueDeviceAttestationSupported()}.
     *
     * <p>Device owner, profile owner and their delegated certificate installer can use
     * {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device information
     * including manufacturer, model, brand, device and product in the attestation record.
     * <p>Device owner, profile owner, their delegated certificate installer and the credential
     * management app can use {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device
     * information including manufacturer, model, brand, device and product in the attestation
     * record.
     * Only device owner, profile owner on an organization-owned device and their delegated
     * certificate installers can use {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and
     * {@link #ID_TYPE_MEID} to request unique device identifiers to be attested (the serial number,
@@ -5455,9 +5520,11 @@ public class DevicePolicyManager {
     *        {@code keySpec}.
     * @return A non-null {@code AttestedKeyPair} if the key generation succeeded, null otherwise.
     * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
     *         owner. If Device ID attestation is requested (using {@link #ID_TYPE_SERIAL},
     *         {@link #ID_TYPE_IMEI} or {@link #ID_TYPE_MEID}), the caller must be the Device Owner
     *         or the Certificate Installer delegate.
     *         owner, or {@code admin} is null but the calling application is not a delegated
     *         certificate installer or credential management app. If Device ID attestation is
     *         requested (using {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} or
     *         {@link #ID_TYPE_MEID}), the caller must be the Device Owner or the Certificate
     *         Installer delegate.
     * @throws IllegalArgumentException in the following cases:
     *         <p>
     *         <ul>
@@ -5620,10 +5687,19 @@ public class DevicePolicyManager {
    }
    /**
     * Called by a device or profile owner, or delegated certificate installer, to associate
     * certificates with a key pair that was generated using {@link #generateKeyPair}, and
     * set whether the key is available for the user to choose in the certificate selection
     * prompt.
     * This API can be called by the following to associate certificates with a key pair that was
     * generated using {@link #generateKeyPair}, and set whether the key is available for the user
     * to choose in the certificate selection prompt:
     * <ul>
     *    <li>Device owner</li>
     *    <li>Profile owner</li>
     *    <li>Delegated certificate installer</li>
     *    <li>Credential management app</li>
     * </ul>
     *
     * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app
     * can call this API. If called by the credential management app, the componentName must be
     * {@code null}. Note, there can only be a credential management app on an unmanaged device.
     *
     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
     *            {@code null} if calling from a delegated certificate installer.
@@ -5641,7 +5717,7 @@ public class DevicePolicyManager {
     *        successfully associated with it, {@code false} otherwise.
     * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
     *         owner, or {@code admin} is null but the calling application is not a delegated
     *         certificate installer.
     *         certificate installer or credential management app.
     */
    public boolean setKeyPairCertificate(@Nullable ComponentName admin,
            @NonNull String alias, @NonNull List<Certificate> certs, boolean isUserSelectable) {
+77 −4
Original line number Diff line number Diff line
@@ -233,6 +233,7 @@ import android.provider.ContactsInternal;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Telephony;
import android.security.AppUriAuthenticationPolicy;
import android.security.IKeyChainAliasCallback;
import android.security.IKeyChainService;
import android.security.KeyChain;
@@ -1162,6 +1163,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
            return LocalServices.getService(PersistentDataBlockManagerInternal.class);
        }
        AppOpsManager getAppOpsManager() {
            return mContext.getSystemService(AppOpsManager.class);
        }
        LockSettingsInternal getLockSettingsInternal() {
            return LocalServices.getService(LockSettingsInternal.class);
        }
@@ -5032,7 +5037,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        final CallerIdentity caller = getCallerIdentity(who, callerPackage);
        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
                && (isProfileOwner(caller) || isDeviceOwner(caller)))
                || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL)));
                || (caller.hasPackage() && (isCallerDelegate(caller, DELEGATION_CERT_INSTALL)
                || isCredentialManagementApp(caller, alias, isUserSelectable))));
        final long id = mInjector.binderClearCallingIdentity();
        try {
@@ -5072,7 +5078,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        final CallerIdentity caller = getCallerIdentity(who, callerPackage);
        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
                && (isProfileOwner(caller) || isDeviceOwner(caller)))
                || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL)));
                || (caller.hasPackage() && (isCallerDelegate(caller, DELEGATION_CERT_INSTALL)
                || isCredentialManagementApp(caller, alias))));
        final long id = Binder.clearCallingIdentity();
        try {
@@ -5276,7 +5283,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        } else {
            Preconditions.checkCallAuthorization((caller.hasAdminComponent()
                    && (isProfileOwner(caller) || isDeviceOwner(caller)))
                    || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL)));
                    || (caller.hasPackage() && (isCallerDelegate(caller, DELEGATION_CERT_INSTALL)
                    || isCredentialManagementApp(caller, alias))));
        }
        // As the caller will be granted access to the key, ensure no UID was specified, as
@@ -5372,7 +5380,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        final CallerIdentity caller = getCallerIdentity(who, callerPackage);
        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
                && (isProfileOwner(caller) || isDeviceOwner(caller)))
                || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL)));
                || (caller.hasPackage() && (isCallerDelegate(caller, DELEGATION_CERT_INSTALL)
                || isCredentialManagementApp(caller, alias))));
        final long id = mInjector.binderClearCallingIdentity();
        try (final KeyChainConnection keyChainConnection =
@@ -5807,6 +5816,70 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        }
    }
    /**
     * Check whether a caller application is the credential management app, which can access
     * privileged APIs.
     * <p>
     * This is done by checking that the calling package is authorized to perform the app operation
     * {@link android.app.AppOpsManager#OP_MANAGE_CREDENTIALS}. The alias provided must be contained
     * in the aliases specified in the credential management app's authentication policy. The
     * key pair to install must not be user selectable.
     *
     * @param caller the calling identity
     * @return {@code true} if the calling process is the credential management app.
     */
    private boolean isCredentialManagementApp(CallerIdentity caller, String alias,
            boolean isUserSelectable) {
        // Should not be user selectable
        if (isUserSelectable) {
            Log.e(LOG_TAG, "The credential management app is not allowed to install a "
                    + "user selectable key pair");
            return false;
        }
        return isCredentialManagementApp(caller, alias);
    }
    /**
     * Check whether a caller application is the credential mangement app, which can access
     * privileged APIs.
     * <p>
     * This is done by checking that the calling package is authorized to perform the app operation
     * {@link android.app.AppOpsManager#OP_MANAGE_CREDENTIALS}. The alias provided must be contained
     * in the aliases specified in the credential management app's authentication policy.
     *
     * @param caller the calling identity
     * @return {@code true} if the calling process is the credential management app.
     */
    private boolean isCredentialManagementApp(CallerIdentity caller, String alias) {
        // Should include alias in authentication policy
        try (KeyChainConnection connection = KeyChain.bindAsUser(mContext,
                caller.getUserHandle())) {
            if (!containsAlias(connection.getService().getCredentialManagementAppPolicy(), alias)) {
                return false;
            }
        } catch (RemoteException | InterruptedException e) {
            return false;
        }
        AppOpsManager appOpsManager = mInjector.getAppOpsManager();
        return appOpsManager != null
                ? appOpsManager.noteOpNoThrow(AppOpsManager.OP_MANAGE_CREDENTIALS, caller.getUid(),
                caller.getPackageName(), null, null) == AppOpsManager.MODE_ALLOWED
                : false;
    }
    private static boolean containsAlias(AppUriAuthenticationPolicy policy, String alias) {
        for (Map.Entry<String, Map<Uri, String>> appsToUris :
                policy.getAppAndUriMappings().entrySet()) {
            for (Map.Entry<Uri, String> urisToAliases : appsToUris.getValue().entrySet()) {
                if (urisToAliases.getValue().equals(alias)) {
                    return true;
                }
            }
        }
        return false;
    }
    @Override
    public void setCertInstallerPackage(ComponentName who, String installerPackage)
            throws SecurityException {