diff --git a/api/current.txt b/api/current.txt index 5b64324229d26..0e25fdc3444c7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5347,6 +5347,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); @@ -27265,6 +27266,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 c62f3bc78ac98..58755ac3929b2 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5439,6 +5439,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); @@ -28855,6 +28856,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 e9cce51ec2a46..9645f57fc9da7 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 @@ -223,7 +224,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 { public void onLockTaskModeExiting(Context context, Intent intent) { } + /** + * 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 @@ -431,6 +473,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 67bca4ec08d1e..2179dff25c561 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 dfa41e8af2aa1..e9c24ddc7afb8 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -123,6 +123,12 @@ public final class KeyChain { */ public static final String EXTRA_PORT = "port"; + /** + * 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 @@ -224,6 +230,51 @@ public final class KeyChain { * selected alias or null will be returned via the * KeyChainAliasCallback callback. * + *
The device or profile owner can intercept this before the activity + * is shown, to pick a specific private key alias. + * + *
{@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. + * + *
{@code host} and {@code port} may be used to give the user + * more context about the server requesting the credentials. + * + *
{@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. + * + *
The device or profile owner can intercept this before the activity + * is shown, to pick a specific private key alias.
+ * *{@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 bec0f6644a0ac..a91e5c2e14ac4 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