summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt2
-rw-r--r--api/system-current.txt2
-rw-r--r--core/java/android/app/admin/DeviceAdminReceiver.java53
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl1
-rw-r--r--keystore/java/android/security/KeyChain.java56
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java54
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();