How to use Bouncy Castle Lightweight API's TLSClient


In a previous article I explained how to get Bouncy Castle compiling into a BlackBerry application. This article takes things one step further and demonstrates how to use the TLS functionality in the Bouncy Castle light-weight API to examine server certificate chains.

References

 

Introduction

I started looking into Bouncy Castle Light Weight API when I noticed that the APIs provided with the BlackBerry did not allow me to retrieve more than just basic information about a certificate. I need to get access to the certificate's encoded bytes, which is something that the RIM API doesn't allow me to do. Fortunately BouncyCastle provides a TLS namespace to help me get what I need.

As always, the catch with Bouncy Castle is finding documentation. In this case it looks like Bouncy Castle has deprecated an old way (Note the deprecated keyword) of doing this and implemented a new system which (AFAIK) isn't documented anywhere, not even in the test classes provided with BC (at least as of 1.47).

Another interesting thing to note is that the 'new' way isn't totally free from the 'old' way- There is one place where you have to use an X509CertificateStructure[] (Deprecated) or you won't be able to get back information about server certificates. There is a message inside the X509CertificateStructure source file which indicates that you should use org.bouncycastle.asn1.x509.Certificate as a replacement, but Bouncy Castle itself isn't using this in the TLS namespace and it doesn't look like I can just drop-in the replacement type without refactoring the BC code. Hopefully this will be resolved in a later release.

 

Bouncy Castle TLS Classes and Flow

Establishing a TLS connection using Bouncy Castle requires the use of a few classes:

  • Certificate (org.bouncycastle.crypto.tls.Certificate)
  • TlsProtocolHandler (org.bouncycastle.crypto.tls.TlsProtocolHandler)
  • DefaultTlsClient (or TlsClient) (org.bouncycastle.crypto.tls.DefaultTlsClient)
  • TlsAuthentication (org.bouncycastle.crypto.tls.TlsAuthentication)
  • TlsCredentials (org.bouncycastle.crypto.tls.TlsCredentials)

The only place I was able to find any information on what these classes are and how to use them is from this post to the Bouncy-castle email list. As you can see, there isn't an example there: I had to play around until I got code that worked.

I want to give a high-level overview of how this works before showing the source-code (next section):

  • Setup a SocketConnection (javax.microedition.io.SocketConnection) using Connector.open(...) 
  • Create a TlsProtocolHandler which uses the input and output streams provided by the SocketConnection
  • Setup a DefaultTlsClient with a new TlsAuthentication object inside of the getAuthentication() method (Eclipse will auto-generate a stub method).
    • This is where you will be able to access server certificates. I recommend saving the certs to a class variable for use in other parts of the program
  • Tell the TlsProtocolHandler to connect and pass in the DefaultTlsClient
  • Use the getCerts() method (instance method of Certificate) to retrieve the X509CertificateStructures
  • Use the getEncoded() method to get the DER encoded certificate bytes

 

Sample Bouncy Castle TLS Client Code

Here is a sample class tlstester which illustrates the concepts discussed above.

package com.tlstester;

import java.io.IOException;

import javax.microedition.io.Connector;
import javax.microedition.io.SocketConnection;

import org.bouncycastle.asn1.x509.X509CertificateStructure;
import org.bouncycastle.crypto.tls.Certificate;
import org.bouncycastle.crypto.tls.CertificateRequest;
import org.bouncycastle.crypto.tls.DefaultTlsClient;
import org.bouncycastle.crypto.tls.TlsAuthentication;
import org.bouncycastle.crypto.tls.TlsCredentials;
import org.bouncycastle.crypto.tls.TlsProtocolHandler;

public class TlsTest {
    // placeholder for certificate
    Certificate certificate = null;

    // test method to connect to a socket and retrieve the certificate(s) that are available
    public void scanCertificate(String host, int port, boolean wifi){
        String hostPort = host + ":" + Integer.toString(port);
        SocketConnection soc = null;
        TlsProtocolHandler h = null;

        // Setup a socket over Wifi or Cellular data network
        try {
            if(wifi){
                soc = (SocketConnection) Connector.open("socket://" + hostPort + ";deviceside=true;interface=wifi");
            } else {
                soc = (SocketConnection) Connector.open("socket://" + hostPort + ";deviceside=true");
            }

            // this chain of TlsProtocolHandler -> DefaultTlsClient -> TlsAuthentication will return the certs on the socket
            h = new TlsProtocolHandler(soc.openInputStream(), soc.openOutputStream());
            DefaultTlsClient client = new DefaultTlsClient() {
                public TlsAuthentication getAuthentication() throws IOException {
                    TlsAuthentication auth = new TlsAuthentication() {
                        public void notifyServerCertificate(
                                org.bouncycastle.crypto.tls.Certificate serverCertificate)
                                throws IOException {
                                                                // Capture the server certificate information!
                                certificate = serverCertificate;
                        }

                        public TlsCredentials getClientCredentials(
                                CertificateRequest certificateRequest) throws IOException {
                            // TODO Auto-generated method stub
                            return null;
                        }
                    };
                    return auth;
                }
            };

        // Tell the TlsProtocolHandler to attempt a TLS connection
            h.connect(client);

 // Get the Certificate Array (All certs that are listening on the socket)
            X509CertificateStructure[] certs = certificate.getCerts();

            // Get the DER encoded bytes of the certificate
            certs[0].getEncoded();

            // close out the connection
            h.close();
            soc.close();

        } catch (IOException e) {
            // If something 'bad' happens, close out the connections before exiting
            try{
                h.close();
                soc.close();
            } catch(Exception e2){
            }
        }
    }
}

I am fairly satisfied with this method, except for where I rely on a deprecated class. Hopefully that will be remedied at some point. When they finally remove the X509CertificateStructure I hope their replacement class 'fits' well.