diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 8e305e0b9784f..5eb1922a163c6 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -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: + *
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. * *
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: + *
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. * *
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: + *
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 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: + *
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: + *
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. * *
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()}. * - *
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. + *
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: *
*
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
+ * 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.
+ *
+ * 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