How to create a BKS (BouncyCastle) format Java Keystore that contains a client certificate chain
Detailed Step by Step instructions I followed to achieve this
- Download bouncycastle JAR from http://repo2.maven.org/maven2/org/bouncycastle/bcprov-ext-jdk15on/1.46/bcprov-ext-jdk15on-1.46.jaror take it from the "doc" folder.
- Configure BouncyCastle for PC using one of the below methods.
- Adding the BC Provider Statically (Recommended)
- Copy the bcprov-ext-jdk15on-1.46.jar to each
- D:\tools\jdk1.5.0_09\jre\lib\ext (JDK (bundled JRE)
- D:\tools\jre1.5.0_09\lib\ext (JRE)
- C:\ (location to be used in env variable)
- Modify the java.security file under
- D:\tools\jdk1.5.0_09\jre\lib\security
- D:\tools\jre1.5.0_09\lib\security
- and add the following entry
- security.provider.7=org.bouncycastle.jce.provider.BouncyCastleProvider
- Add the following environment variable in "User Variables" section
- CLASSPATH=%CLASSPATH%;c:\bcprov-ext-jdk15on-1.46.jar
- Copy the bcprov-ext-jdk15on-1.46.jar to each
- Add bcprov-ext-jdk15on-1.46.jar to CLASSPATH of your project and Add the following line in your code
- Security.addProvider(new BouncyCastleProvider());
- Adding the BC Provider Statically (Recommended)
- Generate the Keystore using Bouncy Castle
- Run the following command
- keytool -genkey -alias myproject -keystore C:/myproject.keystore -storepass myproject -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
- This generates the file C:\myproject.keystore
- Run the following command to check if it is properly generated or not
- keytool -list -keystore C:\myproject.keystore -storetype BKS
- Run the following command
Configure BouncyCastle for TOMCAT
Open D:\tools\apache-tomcat-6.0.35\conf\server.xml and add the following entry
- <Connectorport="8443"keystorePass="myproject"alias="myproject"keystore="c:/myproject.keystore"keystoreType="BKS"SSLEnabled="true"clientAuth="false"protocol="HTTP/1.1"scheme="https"secure="true"sslProtocol="TLS"sslImplementationName="org.bouncycastle.jce.provider.BouncyCastleProvider"/>
Restart the server after these changes.
- Configure BouncyCastle for Android Client
- No need to configure since Android supports Bouncy Castle Version 1.46 internally in the provided "android.jar".
- Just implement your version of HTTP Client (MyHttpClient.java can be found below) and set the following in code
- SSLSocketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
- If you don't do this, it gives an exception as below
- javax.net.ssl.SSLException: hostname in certificate didn't match: <192.168.104.66> !=
- In production mode, change the above code to
- SSLSocketFactory.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
MyHttpClient.java
package com.arisglobal.aglite.network;import java.io.InputStream;import java.security.KeyStore;import org.apache.http.conn.ClientConnectionManager;import org.apache.http.conn.scheme.PlainSocketFactory;import org.apache.http.conn.scheme.Scheme;import org.apache.http.conn.scheme.SchemeRegistry;import org.apache.http.conn.ssl.SSLSocketFactory;import org.apache.http.impl.client.DefaultHttpClient;import org.apache.http.impl.conn.SingleClientConnManager;import com.arisglobal.aglite.activity.R;import android.content.Context;public class MyHttpClient extends DefaultHttpClient { final Context context; public MyHttpClient(Context context) { this.context = context; } @Override protected ClientConnectionManager createClientConnectionManager() { SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); // Register for port 443 our SSLSocketFactory with our keystore to the ConnectionManager registry.register(new Scheme("https", newSslSocketFactory(), 443)); return new SingleClientConnManager(getParams(), registry); } private SSLSocketFactory newSslSocketFactory() { try { // Get an instance of the Bouncy Castle KeyStore format KeyStore trusted = KeyStore.getInstance("BKS"); // Get the raw resource, which contains the keystore with your trusted certificates (root and any intermediate certs) InputStream in = context.getResources().openRawResource(R.raw.aglite); try { // Initialize the keystore with the provided trusted certificates. // Also provide the password of the keystore trusted.load(in, "aglite".toCharArray()); } finally { in.close(); } // Pass the keystore to the SSLSocketFactory. The factory is responsible for the verification of the server certificate. SSLSocketFactory sf = new SSLSocketFactory(trusted); // Hostname verification from certificate // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506 sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); return sf; } catch (Exception e) { throw new AssertionError(e); } }}
How to invoke the above code in your Activity class:
DefaultHttpClient client = new MyHttpClient(getApplicationContext());HttpResponse response = client.execute(...);
I don't think your problem is with the BouncyCastle keystore; I think the problem is with a broken javax.net.ssl package in Android. The BouncyCastle keystore is a supreme annoyance because Android changed a default Java behavior without documenting it anywhere -- and removed the default provider -- but it does work.
Note that for SSL authentication you may require 2 keystores. The "TrustManager" keystore, which contains the CA certs, and the "KeyManager" keystore, which contains your client-site public/private keys. (The documentation is somewhat vague on what needs to be in the KeyManager keystore.) In theory, you shouldn't need the TrustManager keystore if all of your certficates are signed by "well-known" Certifcate Authorities, e.g., Verisign, Thawte, and so on. Let me know how that works for you. Your server will also require the CA for whatever was used to sign your client.
I could not create an SSL connection using javax.net.ssl at all. I disabled the client SSL authentication on the server side, and I still could not create the connection. Since my end goal was an HTTPS GET, I punted and tried using the Apache HTTP Client that's bundled with Android. That sort-of worked. I could make the HTTPS conection, but I still could not use SSL auth. If I enabled the client SSL authentication on my server, the connection would fail. I haven't checked the Apache HTTP Client code, but I suspect they are using their own SSL implementation, and don't use javax.net.ssl.