diff options
-rw-r--r-- | api/current.txt | 2 | ||||
-rw-r--r-- | api/system-current.txt | 2 | ||||
-rw-r--r-- | core/java/android/app/admin/DeviceAdminReceiver.java | 53 | ||||
-rw-r--r-- | core/java/android/app/admin/IDevicePolicyManager.aidl | 1 | ||||
-rw-r--r-- | keystore/java/android/security/KeyChain.java | 56 | ||||
-rw-r--r-- | services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java | 54 |
6 files changed, 166 insertions, 2 deletions
diff --git a/api/current.txt b/api/current.txt index 3034650..ae93e2b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5404,6 +5404,7 @@ package android.app.admin { ctor public DeviceAdminReceiver(); method public android.app.admin.DevicePolicyManager getManager(android.content.Context); method public android.content.ComponentName getWho(android.content.Context); + method public java.lang.String onChoosePrivateKeyAlias(android.content.Context, android.content.Intent, long, java.lang.String, int, java.lang.String, java.lang.String); method public java.lang.CharSequence onDisableRequested(android.content.Context, android.content.Intent); method public void onDisabled(android.content.Context, android.content.Intent); method public void onEnabled(android.content.Context, android.content.Intent); @@ -27316,6 +27317,7 @@ package android.security { public final class KeyChain { ctor public KeyChain(); method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String); + method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String, java.lang.String); method public static android.content.Intent createInstallIntent(); method public static java.security.cert.X509Certificate[] getCertificateChain(android.content.Context, java.lang.String) throws java.lang.InterruptedException, android.security.KeyChainException; method public static java.security.PrivateKey getPrivateKey(android.content.Context, java.lang.String) throws java.lang.InterruptedException, android.security.KeyChainException; diff --git a/api/system-current.txt b/api/system-current.txt index 567ff3e..6c4d28e 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5498,6 +5498,7 @@ package android.app.admin { ctor public DeviceAdminReceiver(); method public android.app.admin.DevicePolicyManager getManager(android.content.Context); method public android.content.ComponentName getWho(android.content.Context); + method public java.lang.String onChoosePrivateKeyAlias(android.content.Context, android.content.Intent, long, java.lang.String, int, java.lang.String, java.lang.String); method public java.lang.CharSequence onDisableRequested(android.content.Context, android.content.Intent); method public void onDisabled(android.content.Context, android.content.Intent); method public void onEnabled(android.content.Context, android.content.Intent); @@ -28914,6 +28915,7 @@ package android.security { public final class KeyChain { ctor public KeyChain(); method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String); + method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String, java.lang.String); method public static android.content.Intent createInstallIntent(); method public static java.security.cert.X509Certificate[] getCertificateChain(android.content.Context, java.lang.String) throws java.lang.InterruptedException, android.security.KeyChainException; method public static java.security.PrivateKey getPrivateKey(android.content.Context, java.lang.String) throws java.lang.InterruptedException, android.security.KeyChainException; diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index 381d851..a3d96bd 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -25,6 +25,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.security.KeyChain; /** * Base class for implementing a device administration component. This @@ -222,7 +223,28 @@ public class DeviceAdminReceiver extends BroadcastReceiver { public static final String ACTION_PROFILE_PROVISIONING_COMPLETE = "android.app.action.PROFILE_PROVISIONING_COMPLETE"; - /** + /** @hide */ + public static final String ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS"; + + /** @hide */ + public static final String EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID = "android.app.extra.CHOOSE_PRIVATE_KEY_SENDER_UID"; + + /** @hide */ + public static final String EXTRA_CHOOSE_PRIVATE_KEY_HOST = "android.app.extra.CHOOSE_PRIVATE_KEY_HOST"; + + /** @hide */ + public static final String EXTRA_CHOOSE_PRIVATE_KEY_PORT = "android.app.extra.CHOOSE_PRIVATE_KEY_PORT"; + + /** @hide */ + public static final String EXTRA_CHOOSE_PRIVATE_KEY_URL = "android.app.extra.CHOOSE_PRIVATE_KEY_URL"; + + /** @hide */ + public static final String EXTRA_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.extra.CHOOSE_PRIVATE_KEY_ALIAS"; + + /** @hide */ + public static final String EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE = "android.app.extra.CHOOSE_PRIVATE_KEY_RESPONSE"; + + /** * Name under which a DevicePolicy component publishes information * about itself. This meta-data must reference an XML resource containing * a device-admin tag. XXX TO DO: describe syntax. @@ -402,6 +424,26 @@ public class DeviceAdminReceiver extends BroadcastReceiver { } /** + * Allows this receiver to select the alias for a private key and certificate pair for + * authentication. If this method returns null, the default {@link android.app.Activity} will be + * shown that lets the user pick a private key and certificate pair. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param uid The uid asking for the private key and certificate pair. + * @param host The authentication host, may be null. + * @param port The authentication port, or -1. + * @param url The URL to authenticate, may be null. + * @param alias The alias preselected by the client, or null. + * @return The private key alias to return and grant access to. + * @see KeyChain#choosePrivateKeyAlias + */ + public String onChoosePrivateKeyAlias(Context context, Intent intent, long uid, String host, + int port, String url, String alias) { + return null; + } + + /** * Intercept standard device administrator broadcasts. Implementations * should not override this method; it is better to implement the * convenience callbacks for each action. @@ -430,6 +472,15 @@ public class DeviceAdminReceiver extends BroadcastReceiver { onPasswordExpiring(context, intent); } else if (ACTION_PROFILE_PROVISIONING_COMPLETE.equals(action)) { onProfileProvisioningComplete(context, intent); + } else if (ACTION_CHOOSE_PRIVATE_KEY_ALIAS.equals(action)) { + long uid = intent.getLongExtra(EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, -1); + String host = intent.getStringExtra(EXTRA_CHOOSE_PRIVATE_KEY_HOST); + int port = intent.getIntExtra(EXTRA_CHOOSE_PRIVATE_KEY_PORT, -1); + String url = intent.getStringExtra(EXTRA_CHOOSE_PRIVATE_KEY_URL); + String alias = intent.getStringExtra(EXTRA_CHOOSE_PRIVATE_KEY_ALIAS); + String chosenAlias = onChoosePrivateKeyAlias(context, intent, uid, host, port, url, + alias); + setResultData(chosenAlias); } else if (ACTION_LOCK_TASK_ENTERING.equals(action)) { String pkg = intent.getStringExtra(EXTRA_LOCK_TASK_PACKAGE); onLockTaskModeEntering(context, intent, pkg); diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 67bca4e..2179dff 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -129,6 +129,7 @@ interface IDevicePolicyManager { void enforceCanManageCaCerts(in ComponentName admin); boolean installKeyPair(in ComponentName who, in byte[] privKeyBuffer, in byte[] certBuffer, String alias); + void choosePrivateKeyAlias(in String host, int port, in String url, in String alias, IBinder aliasCallback); void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity); void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName); diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index dfa41e8..e9c24dd 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -127,6 +127,12 @@ public final class KeyChain { * Extra for use with {@link #ACTION_CHOOSER} * @hide Also used by KeyChainActivity implementation */ + public static final String EXTRA_URL = "url"; + + /** + * Extra for use with {@link #ACTION_CHOOSER} + * @hide Also used by KeyChainActivity implementation + */ public static final String EXTRA_ALIAS = "alias"; /** @@ -224,6 +230,51 @@ public final class KeyChain { * selected alias or null will be returned via the * KeyChainAliasCallback callback. * + * <p>The device or profile owner can intercept this before the activity + * is shown, to pick a specific private key alias. + * + * <p>{@code keyTypes} and {@code issuers} may be used to + * highlight suggested choices to the user, although to cope with + * sometimes erroneous values provided by servers, the user may be + * able to override these suggestions. + * + * <p>{@code host} and {@code port} may be used to give the user + * more context about the server requesting the credentials. + * + * <p>{@code alias} allows the chooser to preselect an existing + * alias which will still be subject to user confirmation. + * + * @param activity The {@link Activity} context to use for + * launching the new sub-Activity to prompt the user to select + * a private key; used only to call startActivity(); must not + * be null. + * @param response Callback to invoke when the request completes; + * must not be null + * @param keyTypes The acceptable types of asymmetric keys such as + * "RSA" or "DSA", or a null array. + * @param issuers The acceptable certificate issuers for the + * certificate matching the private key, or null. + * @param host The host name of the server requesting the + * certificate, or null if unavailable. + * @param port The port number of the server requesting the + * certificate, or -1 if unavailable. + * @param alias The alias to preselect if available, or null if + * unavailable. + */ + public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, + String[] keyTypes, Principal[] issuers, String host, int port, String alias) { + choosePrivateKeyAlias(activity, response, keyTypes, issuers, host, port, null, alias); + } + + /** + * Launches an {@code Activity} for the user to select the alias + * for a private key and certificate pair for authentication. The + * selected alias or null will be returned via the + * KeyChainAliasCallback callback. + * + * <p>The device or profile owner can intercept this before the activity + * is shown, to pick a specific private key alias.</p> + * * <p>{@code keyTypes} and {@code issuers} may be used to * highlight suggested choices to the user, although to cope with * sometimes erroneous values provided by servers, the user may be @@ -249,12 +300,14 @@ public final class KeyChain { * certificate, or null if unavailable. * @param port The port number of the server requesting the * certificate, or -1 if unavailable. + * @param url The full url the server is requesting the certificate + * for, or null if unavailable. * @param alias The alias to preselect if available, or null if * unavailable. */ public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, String[] keyTypes, Principal[] issuers, - String host, int port, + String host, int port, String url, String alias) { /* * TODO currently keyTypes, issuers are unused. They are meant @@ -283,6 +336,7 @@ public final class KeyChain { intent.putExtra(EXTRA_RESPONSE, new AliasResponse(response)); intent.putExtra(EXTRA_HOST, host); intent.putExtra(EXTRA_PORT, port); + intent.putExtra(EXTRA_URL, url); intent.putExtra(EXTRA_ALIAS, alias); // the PendingIntent is used to get calling package name intent.putExtra(EXTRA_SENDER, PendingIntent.getActivity(activity, 0, new Intent(), 0)); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index fbb6f7c..1381eef 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -78,6 +78,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.security.Credentials; +import android.security.IKeyChainAliasCallback; import android.security.IKeyChainService; import android.security.KeyChain; import android.security.KeyChain.KeyChainConnection; @@ -2980,6 +2981,59 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } + @Override + public void choosePrivateKeyAlias(final String host, int port, final String url, + final String alias, final IBinder response) { + final ComponentName profileOwner = getProfileOwner(UserHandle.getCallingUserId()); + final UserHandle caller = Binder.getCallingUserHandle(); + final int callerUid = Binder.getCallingUid(); + + if (profileOwner == null) { + sendPrivateKeyAliasResponse(null, response); + return; + } + + Intent intent = new Intent(DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS); + intent.setComponent(profileOwner); + intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, callerUid); + intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_HOST, host); + intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_PORT, port); + intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_URL, url); + intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_ALIAS, alias); + intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE, response); + + final long id = Binder.clearCallingIdentity(); + try { + mContext.sendOrderedBroadcastAsUser(intent, caller, null, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String chosenAlias = getResultData(); + sendPrivateKeyAliasResponse(chosenAlias, response); + } + }, null, Activity.RESULT_OK, null, null); + } finally { + Binder.restoreCallingIdentity(id); + } + } + + private void sendPrivateKeyAliasResponse(final String alias, final IBinder responseBinder) { + final IKeyChainAliasCallback keyChainAliasResponse = + IKeyChainAliasCallback.Stub.asInterface(responseBinder); + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... unused) { + try { + keyChainAliasResponse.alias(alias); + } catch (Exception e) { + // Catch everything (not just RemoteException): caller could throw a + // RuntimeException back across processes. + Log.e(LOG_TAG, "error while responding to callback", e); + } + return null; + } + }.execute(); + } + private void wipeDataLocked(boolean wipeExtRequested, String reason) { // If the SD card is encrypted and non-removable, we have to force a wipe. boolean forceExtWipe = !Environment.isExternalStorageRemovable() && isExtStorageEncrypted(); |