summaryrefslogtreecommitdiffstats
path: root/keystore/java/android/security/KeyChain.java
diff options
context:
space:
mode:
Diffstat (limited to 'keystore/java/android/security/KeyChain.java')
-rw-r--r--keystore/java/android/security/KeyChain.java152
1 files changed, 141 insertions, 11 deletions
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 4f1596d..6229331 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -22,6 +22,7 @@ import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -44,8 +45,12 @@ import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
+import libcore.util.Objects;
+import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore;
/**
* The {@code KeyChain} class provides access to private keys and
@@ -76,6 +81,13 @@ import java.util.concurrent.LinkedBlockingQueue;
* avoid prompting the user with {@link #choosePrivateKeyAlias
* choosePrivateKeyAlias} on subsequent connections. If the alias is
* no longer valid, null will be returned on lookups using that value
+ *
+ * <p>An application can request the installation of private keys and
+ * certificates via the {@code Intent} provided by {@link
+ * #createInstallIntent}. Private keys installed via this {@code
+ * Intent} will be accessible via {@link #choosePrivateKeyAlias} while
+ * Certificate Authority (CA) certificates will be trusted by all
+ * applications through the default {@code X509TrustManager}.
*/
// TODO reference intent for credential installation when public
public final class KeyChain {
@@ -88,11 +100,109 @@ public final class KeyChain {
public static final String ACCOUNT_TYPE = "com.android.keychain";
/**
+ * Action to bring up the KeyChainActivity
+ */
+ private static final String ACTION_CHOOSER = "com.android.keychain.CHOOSER";
+
+ /**
+ * Extra for use with {@link #ACTION_CHOOSER}
* @hide Also used by KeyChainActivity implementation
*/
public static final String EXTRA_RESPONSE = "response";
/**
+ * Extra for use with {@link #ACTION_CHOOSER}
+ * @hide Also used by KeyChainActivity implementation
+ */
+ public static final String EXTRA_HOST = "host";
+
+ /**
+ * Extra for use with {@link #ACTION_CHOOSER}
+ * @hide Also used by KeyChainActivity implementation
+ */
+ 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_ALIAS = "alias";
+
+ /**
+ * Extra for use with {@link #ACTION_CHOOSER}
+ * @hide Also used by KeyChainActivity implementation
+ */
+ public static final String EXTRA_SENDER = "sender";
+
+ /**
+ * Action to bring up the CertInstaller
+ */
+ private static final String ACTION_INSTALL = "android.credentials.INSTALL";
+
+ /**
+ * Optional extra to specify a {@code String} credential name on
+ * the {@code Intent} returned by {@link #createInstallIntent}.
+ */
+ // Compatible with old com.android.certinstaller.CredentialHelper.CERT_NAME_KEY
+ public static final String EXTRA_NAME = "name";
+
+ /**
+ * Optional extra to specify an X.509 certificate to install on
+ * the {@code Intent} returned by {@link #createInstallIntent}.
+ * The extra value should be a PEM or ASN.1 DER encoded {@code
+ * byte[]}. An {@link X509Certificate} can be converted to DER
+ * encoded bytes with {@link X509Certificate#getEncoded}.
+ *
+ * <p>{@link #EXTRA_NAME} may be used to provide a default alias
+ * name for the installed certificate.
+ */
+ // Compatible with old android.security.Credentials.CERTIFICATE
+ public static final String EXTRA_CERTIFICATE = "CERT";
+
+ /**
+ * Optional extra for use with the {@code Intent} returned by
+ * {@link #createInstallIntent} to specify a PKCS#12 key store to
+ * install. The extra value should be a {@code byte[]}. The bytes
+ * may come from an external source or be generated with {@link
+ * java.security.KeyStore#store} on a "PKCS12" instance.
+ *
+ * <p>The user will be prompted for the password to load the key store.
+ *
+ * <p>The key store will be scanned for {@link
+ * java.security.KeyStore.PrivateKeyEntry} entries and both the
+ * private key and associated certificate chain will be installed.
+ *
+ * <p>{@link #EXTRA_NAME} may be used to provide a default alias
+ * name for the installed credentials.
+ */
+ // Compatible with old android.security.Credentials.PKCS12
+ public static final String EXTRA_PKCS12 = "PKCS12";
+
+ /**
+ * Returns an {@code Intent} that can be used for credential
+ * installation. The intent may be used without any extras, in
+ * which case the user will be able to install credentials from
+ * their own source.
+ *
+ * <p>Alternatively, {@link #EXTRA_CERTIFICATE} or {@link
+ * #EXTRA_PKCS12} maybe used to specify the bytes of an X.509
+ * certificate or a PKCS#12 key store for installation. These
+ * extras may be combined with {@link #EXTRA_NAME} to provide a
+ * default alias name for credentials being installed.
+ *
+ * <p>When used with {@link Activity#startActivityForResult},
+ * {@link Activity#RESULT_OK} will be returned if a credential was
+ * successfully installed, otherwise {@link
+ * Activity#RESULT_CANCELED} will be returned.
+ */
+ public static Intent createInstallIntent() {
+ Intent intent = new Intent(ACTION_INSTALL);
+ intent.setClassName("com.android.certinstaller",
+ "com.android.certinstaller.CertInstallerMain");
+ return intent;
+ }
+
+ /**
* 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
@@ -106,6 +216,9 @@ public final class KeyChain {
* <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.
+ *
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#USE_CREDENTIALS}.
*
@@ -123,14 +236,17 @@ 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 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 alias) {
/*
- * TODO currently keyTypes, issuers, host, and port are
- * unused. They are meant to follow the semantics and purpose
- * of X509KeyManager method arguments.
+ * TODO currently keyTypes, issuers are unused. They are meant
+ * to follow the semantics and purpose of X509KeyManager
+ * method arguments.
*
* keyTypes would allow the list to be filtered and typically
* will be set correctly by the server. In practice today,
@@ -142,11 +258,6 @@ public final class KeyChain {
* server. Others will send none. If this is used, if there
* are no matches after applying the constraint, it should be
* ignored.
- *
- * host and port may be shown to the user if available, but it
- * should be clear that they are not validated values, perhaps
- * shown along with requesting application identity to clarify
- * the source of the request.
*/
if (activity == null) {
throw new NullPointerException("activity == null");
@@ -154,8 +265,13 @@ public final class KeyChain {
if (response == null) {
throw new NullPointerException("response == null");
}
- Intent intent = new Intent("com.android.keychain.CHOOSER");
+ Intent intent = new Intent(ACTION_CHOOSER);
intent.putExtra(EXTRA_RESPONSE, new AliasResponse(activity, response));
+ intent.putExtra(EXTRA_HOST, host);
+ intent.putExtra(EXTRA_PORT, port);
+ 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));
activity.startActivity(intent);
}
@@ -272,7 +388,21 @@ public final class KeyChain {
}
IKeyChainService keyChainService = keyChainConnection.getService();
byte[] certificateBytes = keyChainService.getCertificate(alias, authToken);
- return new X509Certificate[] { toCertificate(certificateBytes) };
+ List<X509Certificate> chain = new ArrayList<X509Certificate>();
+ chain.add(toCertificate(certificateBytes));
+ TrustedCertificateStore store = new TrustedCertificateStore();
+ for (int i = 0; true; i++) {
+ X509Certificate cert = chain.get(i);
+ if (Objects.equal(cert.getSubjectX500Principal(), cert.getIssuerX500Principal())) {
+ break;
+ }
+ X509Certificate issuer = store.findIssuer(cert);
+ if (issuer == null) {
+ break;
+ }
+ chain.add(issuer);
+ }
+ return chain.toArray(new X509Certificate[chain.size()]);
} catch (RemoteException e) {
throw new KeyChainException(e);
} catch (RuntimeException e) {