3
votes

I am trying to understand TLS connection of Android to server. Can anybody correct me?

There are two kinds of starting a TLS connection. First, only server has certificate, and client makes decision to trust it or not. Second, both client and server have got a certificate. Am I right?

How can I generate custom unique certificate for TLS connection on Android device and use it for connection to server? I found only realization of first kind of connection.

Can anybody help me?

1
While not exactly a duplicate, you could check out my question about using client side certificates, here: stackoverflow.com/questions/24406266/…Felix

1 Answers

5
votes

To achieve a two side authentication through TLS, you need to have a keystore on the server and client sides.

I normally use Portecle tool to create the keystores, it's a gui java based tool very easy to use. You can download it on portecle.sourceforge.net

You will need to use that tool to create a keystore in JKS format for the server side, and another keystore in BKS format for the client side. The format is different because Android does not support natively the usage of JKS keystores, only BKS, and viceversa.

Once you have two keystores created, you need to generate a key pair (public and private key) for each one of them. Portecle have a button on the tool bar called under 'Tools' submenu to do that. A common key algorithm and size is RSA 2048 bits. After that you will need to set a few parameters to the certificate, like organization unti, name, locality, etc.

Now you have two keystores, and two keypairs.

To allow the client to decrypt the messages received by the server, it is necessary to provide the client keystore with the public key of the server keystore. To do that just right click the keypair on the server keystore and click on 'Export' option. Then select 'Certificate Chain' and select a location on your filesystem to store the certificate.

Now you have the public key ready to be imported on the client keystore. To do that click on 'Tools' toolbar and select 'Import Trusted Certificate', then look for the certificate file exported on the previous step. Some alert messages will appear indicating you that the trust path could not be established, dont worry about that for now.

So, now you have the client keystore with the keypair, and the server certificate. That's enough on the client side.

Now, it is necessary to import the client keypair into the server keystore. On the client keystore right click the keypair and choose 'Export'. On the opened popup choose 'Private key and certificates', and select PKCS #12 format.

Then, open the server keystore and use the 'Import keypair' submenu of the 'Tools' toolbar, and then select the keypair exported on the previous step.

Remember that is very important to save the client keystore on BKS format, and the server keystore in JKS format.

OK, that's all with the keystores, now it's time to code.

Let's start with server code. The next snippet was extracted from a java Spring project i have running. It's an embedded tomcat, so the configuration is pure java, it is very easy to find how to configure an ssl connector on traditional tomcat configurations , so i will put only the embedded version.

private Connector createSslConnector() {

//print the client keystore
printClientKeystore();

Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
try {

    //read the keystore from the jar
    //and write it to a tmp file
    Resource keystoreResource = context.getResource("classpath:config/server.keystore");
    byte[] keystoreData = readKeystore(keystoreResource.getInputStream());
    File tmpKeystoreFile = File.createTempFile("keystore", "");
    writeKeystore(tmpKeystoreFile, keystoreData);

    //keystore information
    final String keystoreFile = tmpKeystoreFile.getAbsolutePath();
    final String keystorePass = "yourKeystorePass";
    final String keystoreType = "pkcs12";
    final String keystoreProvider = "SunJSSE";
    final String keystoreAlias = "comics_tomcat";

    connector.setScheme("https");
    connector.setAttribute("clientAuth", "true");
    connector.setPort(HTTPS_PORT);
    connector.setSecure(true);
    protocol.setSSLEnabled(true);

    //keystore
    protocol.setKeystoreFile(keystoreFile);
    protocol.setKeystorePass(keystorePass);
    protocol.setKeystoreType(keystoreType);
    protocol.setProperty("keystoreProvider", keystoreProvider);
    protocol.setKeyAlias(keystoreAlias);

    //truststore
    protocol.setTruststoreFile(keystoreFile);
    protocol.setTruststorePass(keystorePass);

    protocol.setPort(HTTPS_PORT);

    return connector;
}
catch (IOException e) {
    LOGGER.error(e.getMessage(), e);
    throw new IllegalStateException("cant access keystore: [" + "keystore"
            + "] or truststore: [" + "keystore" + "]", e);
}
}

That's all for the server side, you have an ssl connector with the secure flag in true by using the 'setSecure(true)' method. This allows you to require user authentication using private/public key auth.

The following code is for the client side, it loads the keystore, the truststore (by using the same keystore), configure a hostname verifier to allow connections to specific domains, and then opens a connection.

            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());

            //load keystore stream
            byte[] keystoreData = readInputStream(getAssets().open("client.keystore"));

            //load keystore
            ByteArrayInputStream bais = new ByteArrayInputStream(keystoreData);
            keyStore.load(bais, KEYSTORE_PASSWORD.toCharArray());
            //load truststore
            bais = new ByteArrayInputStream(keystoreData);
            trustStore.load(bais, KEYSTORE_PASSWORD.toCharArray());
            //load trustmanager
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(trustStore);
            //init keymanager
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(keyStore, KEYSTORE_PASSWORD.toCharArray());
            //create ssl context
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);


HostnameVerifier HOSTNAME_VERIFIER = new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            List<String> allowedHostnames = new ArrayList<String>();
            allowedHostnames.add("pinterest.com");
            allowedHostnames.add("192.168.1.43");
            allowedHostnames.add("10.0.2.2");
            return allowedHostnames.indexOf(hostname) != -1;
        }
    };

                    //open https connection
                    URL url = new URL("https://" + SERVER_URL + ":" + SERVER_PORT + "/api/v1/publication/getDescriptor/" + publicationId);
                    HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
                    urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
                    urlConnection.setHostnameVerifier(HOSTNAME_VERIFIER);

                    //read server response
                    byte[] serverResult = readInputStream(urlConnection.getInputStream());

Let me know if you have any questions!