diff options
Diffstat (limited to 'keystore/java/android/security/KeyChain.java')
-rw-r--r-- | keystore/java/android/security/KeyChain.java | 152 |
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) { |