56

I'm writing an Android app that requires SSL client authentication. I know how to create a JKS keystore for a desktop Java application, but Android only supports the BKS format. Every way I've tried to create the keystore results in the following error:
handling exception: javax.net.ssl.SSLHandshakeException: null cert chain

So it looks like the client is never sending a proper certificate chain, probably because I'm not creating the keystore properly. I'm unable to enable SSL debugging like I can on the desktop, so that's making this much more difficult than it should be.

For reference the following is the command that IS working to create a BKS truststore:
keytool -importcert -v -trustcacerts -file "cacert.pem" -alias ca -keystore "mySrvTruststore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar" -storetype BKS -storepass testtest


Here is the command I've tried that is NOT working to create a BKS client keystore:

cat clientkey.pem clientcert.pem cacert.pem > client.pem

keytool -import -v -file <(openssl x509 -in client.pem) -alias client -keystore "clientkeystore" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar" -storetype BKS -storepass testtest
Ben Baron
  • 14,496
  • 12
  • 55
  • 65
  • 19
    Seriously no one has experience with the BKS format? Argh, why couldn't Android just use the standard JKS format, or at least document this format since it's all they support? This should be simple... – Ben Baron Nov 07 '10 at 23:46

7 Answers7

63

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.jar or 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
    • Add bcprov-ext-jdk15on-1.46.jar to CLASSPATH of your project and Add the following line in your code
      • Security.addProvider(new BouncyCastleProvider());
  • 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
  • Configure BouncyCastle for TOMCAT

    • Open D:\tools\apache-tomcat-6.0.35\conf\server.xml and add the following entry

      • <Connector port="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(...);
Vipul
  • 816
  • 9
  • 11
  • I am following above procedure, I did it fine upto the step "Generate the Keystore using Bouncy Castle", here when running the command keytool, its giving following error, keytool error: java.lang.ClassNotFoundException: org.bouncycastle.jce.provider.BouncyCastleProvider, please suggest – candy May 04 '12 at 10:49
  • I got this step done, by specifying the BouncyCastle library path also in keytool command,thanks – candy May 04 '12 at 13:54
  • I'm no longer working on this project so I haven't verified this myself, but I'm accepting this as the answer because you deserve some points for such a detailed answer and as per candy and your experiences, it appears to be a working solution. Thank you for contributing. – Ben Baron Aug 31 '12 at 19:51
  • If anyone tries this and it doesn't work, please comment, thanks. – Ben Baron Aug 31 '12 at 19:52
  • I got an exception Illegal Key Size. – Joubert Vasconcelos Mar 07 '13 at 16:33
  • This is the best explanation I've ever found. Congrats for the successful work! – xarlymg89 Mar 24 '13 at 15:38
  • If I do this, will break within a few years when the cert has to be reissued? – Ray Britton Jul 15 '13 at 08:06
  • 4
    If you are getting ClassNotFoundException: org.bouncycastle.jce.provider.BouncyCastleProvider then use the -providerpath /bcprov-jdk15on-149.jar – scottyab Aug 16 '13 at 13:33
  • 3
    +1 nice answer. Instead of setting the classpath, just use `-providerpath c:\bcprov-ext-jdk15on-1.46.jar -provider org.bouncycastle.jce.provider.BouncyCastleProvider` in both commads. Also, on checking, you will be asked for a password. It's not the one you entered. It's the one after `storepass`, which is `myproject`. – Eng.Fouad Feb 23 '14 at 06:58
  • This is a very good concise guide to getting a BKS keystore to work. Please note that on Android, the version of the bcprov jar file does matter. It works with 1.46 or 1.47 on Android, newer versions don't seem to work on android. – Christine Mar 04 '14 at 17:30
  • Indeed, thanks to this guide I figured out the following command `keytool -importcert -file chain.crt -alias mykey3 -keystore hello.jks -storetype BKS -providerpath bcprov-jdk15on-151.jar -provider org.bouncycastle.jce.provider.BouncyCastleProvider` also, 1.47+ works too if you use SpongyCastle in your application. – EpicPandaForce Nov 18 '14 at 12:37
  • 3
    If you run into a `java.security.InvalidKeyException: Illegal key size` exception when creating the keystore, you may have to replace your JCE policy files with socalled _Unlimited Strength Jurisdiction Policy File_. [Link to files for Java 7](http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html) – JesperB Dec 05 '14 at 20:39
29

I use Portecle, and it works like a charm.

gnclmorais
  • 4,897
  • 5
  • 30
  • 41
  • I'm using it, since I'm working with KeyStores too, and it works. Ok, it's not perfect, but it helps a lot. – gnclmorais Feb 04 '11 at 19:53
  • 2
    I have used Portecle as well and confirm that it fixed my problem!!! I did have to use HTTPSUrlConnection in my Android project and mess around with TrustManagerFactory a bit though. – Exegesis Jun 15 '11 at 18:05
  • 1
    I just want to note that Portecle 1.7 which, as of writing, is the latest, has an outdated bouncycastle that is not capable of opening newer bks keystores. – Chris May 29 '13 at 16:57
  • Thank you Chris, I was not aware of that. – gnclmorais Jun 02 '13 at 23:27
  • I have used portecle and created bks. Still it is not running. – Jeevan Roy dsouza Dec 05 '13 at 12:12
  • 1
    Amazing! I've been looking for this kind of software for years. EVERYBODY should ditch keytool and use Porteclé instead. By the way, importing a server self-signed certificate and using the code from saxos works very well for me. I have no idea why SSL is such a pain with Java. The OpenSSL C API is not for beginners but at least the code is compact, consistent and you can find a lot of examples online (working from 1989 to 2014, unlike any other Java example out there which doesn't work on your installation and needs new certificates...) – tiktak Jul 08 '14 at 14:58
  • can you elaborate the steps? – Nilabja Jan 08 '18 at 12:58
  • I use this tool here: https://keystore-explorer.org/ which is far superior and convenient. Although it is only U.I based. It supports all kinds of different keystore format. Basically, you simply need to extract your public key certificate from your initial JKS, create a new BKS and import that extracted certificate. Bundle it with Android and you're done. – TheRealChx101 Mar 21 '19 at 03:46
5

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.

Cthulhu
  • 51
  • 2
  • Yes I'm using a separate truststore (created using the command in my question) as well as a keystore that I've created using the second command in my question (also tried creating it a few other ways all without success). I didn't realize that the ssl package in Android could be the culprit. I'll see if I can get any response from the Android folks. – Ben Baron Nov 19 '10 at 05:21
5

Not sure you resolved this issue or not, but this is how I do it and it works on Android:

  1. Use openssl to merge client's cert(cert must be signed by a CA that accepted by server) and private key into a PCKS12 format key pair: openssl pkcs12 -export -in clientcert.pem -inkey clientkey.pem -out client.p12
  2. You may need patch your JRE to umlimited strength encryption depends on your key strength: copy the jar files fromJCE 5.0 unlimited strength Jurisdiction Policy FIles and override those in your JRE (eg.C:\Program Files\Java\jre6\lib\security)
  3. Use Portecle tool mentioned above and create a new keystore with BKS format
  4. Import PCKS12 key pair generated in step 1 and save it as BKS keystore. This keystore works with Android client authentication.
  5. If you need to do certificate chain, you can use this IBM tool:KeyMan to merge client's PCKS12 key pair with CA cert. But it only generate JKS keystore, so you again need Protecle to convert it to BKS format.
Fei
  • 51
  • 1
  • 2
2

command line:

keytool -genseckey -alias aliasName -keystore truststore.bks -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath /path/to/jar/bcprov-jdk16-1.46.jar -storetype BKS
Andrew
  • 36,676
  • 11
  • 141
  • 113
1

Your command for creating the BKS keystore looks correct for me.

How do you initialize the keystore.

You need to craeate and pass your own SSLSocketFactory. Here is an example which uses Apache's org.apache.http.conn.ssl.SSLSocketFactory

But I think you can do pretty the same on the javax.net.ssl.SSLSocketFactory

    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.mykeystore);
        try {
            // Initialize the keystore with the provided trusted certificates
            // Also provide the password of the keystore
            trusted.load(in, "testtest".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.STRICT_HOSTNAME_VERIFIER);
        return sf;
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}

Please let me know if it worked.

saxos
  • 2,467
  • 1
  • 20
  • 21
  • The command that I posted is correct for creating a trust store. And it is working for that purpose. I am trying to create a key store to use client authentication which is not working. I've updated my question to include the command I've tried that is not working. – Ben Baron Nov 17 '10 at 02:09
0

Use this manual http://blog.antoine.li/2010/10/22/android-trusting-ssl-certificates/ This guide really helped me. It is important to observe a sequence of certificates in the store. For example: import the lowermost Intermediate CA certificate first and then all the way up to the Root CA certificate.

pwb
  • 105
  • 1
  • 2
  • 8