Merge "Add credential management app access control DPM"

This commit is contained in:
Alex Johnston
2020-12-01 22:29:47 +00:00
committed by Android (Google) Code Review
2 changed files with 183 additions and 34 deletions

View File

@@ -5240,9 +5240,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
@@ -5273,10 +5286,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
@@ -5314,10 +5340,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
@@ -5343,7 +5385,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
@@ -5376,15 +5419,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
*/
@@ -5419,10 +5473,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.
@@ -5445,9 +5509,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,
@@ -5482,9 +5547,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>
@@ -5647,10 +5714,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.
@@ -5668,7 +5744,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) {

View File

@@ -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;
@@ -1154,6 +1155,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);
}
@@ -5031,7 +5036,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 {
@@ -5071,7 +5077,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 {
@@ -5275,7 +5282,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
@@ -5371,7 +5379,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 =
@@ -5806,6 +5815,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 {