One solution for Android TLS/SSL client authentication

9 Comments

Some time ago our customer has decided to implement a more secure way of transmitting form data from an Android app to a webservice. Previously we used a simple HTTPS connection to send form data via webservice. In updated version customer wanted to use TLS/SSL to authenticate server and clients. The basic operations in TLS/SSL connection are: validation of the identity of the HTTPS server against a list of trusted certificates and client authentication to the HTTPS server using a private key.

We got a client certificate in form of *.p12 file to authenticate to the HTTPS server using a private key. The identity of the HTTPS server was not validated, we trusted to all servers. *.p12 file format is commonly used to store X.509 private keys with accompanying public key certificates, protected with a password-based symmetric key.

In Android development SSLSocketFactory is used to validate the identity of the HTTPS server and to authenticate client to the HTTPS server using a private key. SSLSocketFactory will enable server authentication when supplied with a truststore file containg one or several trusted certificates but in our case we trust to all servers. SSLSocketFactory will enable client authentication when supplied with a keystore file containg a private key/public certificate pair. The client secure socket will use the private key to authenticate itself to the target HTTPS server during the SSL session handshake if requested to do so by the server. The target HTTPS server will in its turn verify the certificate presented by the client in order to establish client’s authenticity. Therefore, the solution for our problem was to create custom SSLSocketFactory . The custom SSLSocketFactory was then used to construct HttpClient that executes HTTP(S) requests.

Custom SSLSocketFactory class:

/**
 *  Custom SSLSocketFactory class.
 */
public class CustomSSLSocketFactory extends SSLSocketFactory {
 
   SSLContext sslContext = SSLContext.getInstance("TLS");
   Context context;
 
   /**
    *  Constructor.
    */
   public CustomSSLSocketFactory(Context context, KeyStore keystore, String keyStorePassword, KeyStore truststore)
			throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
 
        super(keystore, keyStorePassword, truststore);
	this.context = context;
 
	// custom TrustManager,trusts all servers
	TrustManager tm = new X509TrustManager() {
		@Override
		public void checkClientTrusted(X509Certificate[] chain,
                  	String authType) throws CertificateException {
			}
 
			@Override
			public void checkServerTrusted(X509Certificate[] chain,
                  	String authType) throws CertificateException {
			}
 
			@Override
			public X509Certificate[] getAcceptedIssuers() {
				return null;
			}
	};
 
	Log.i("CLIENT CERTIFICATES", "Loaded client certificates: " + keystore.size());
 
	// initialize key manager factory with the client certificate
	KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keystore,"mypassword".toCharArray());
 
	sslContext.init(keyManagerFactory.getKeyManagers(), new TrustManager[] { tm }, null);
   }
 
   @Override
   public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
	return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
   }
 
   @Override
   public Socket createSocket() throws IOException {
	return sslContext.getSocketFactory().createSocket();
   }
 
   /**
    *	Create new HttpClient with CustomSSLSocketFactory.
    */
   public static HttpClient getNewHttpClient(Context context) {
	try {
		KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
		trustStore.load(null, null);
 
                // client certificate is stored in android's resource folder (raw)
		InputStream keyStoreStream =  context.getResources().openRawResource(R.raw.p12_file);
		KeyStore keyStore = KeyStore.getInstance("PKCS12");
 
		try {
			keyStore.load(keyStoreStream, "mypassword".toCharArray());
		} catch (CertificateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
 
		SSLSocketFactory sf = new CustomSSLSocketFactory(context,keyStore, "mypassword", trustStore);
		sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
 
		HttpParams params = new BasicHttpParams();
		HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
		HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
 
		SchemeRegistry registry = new SchemeRegistry();
		registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
		registry.register(new Scheme("https", sf, 443));
 
		ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
 
		return new DefaultHttpClient(ccm, params);
 
	} catch (Exception e) {
		return new DefaultHttpClient();
	}
   }
}

After that we can easily execute HTTP(S) post:

HttpClient client = CustomSSLSocketFactory.getNewHttpClient(MyActivity.this);
String formDataServiceUrl = getString(R.string.form_data_service_url);
HttpPost post = new HttpPost(formDataServiceUrl);
post.setEntity(getMultipartEntityForPost());
 
HttpResponse result = client.execute(post);

Kommentare

  • Lily

    8. May 2012 von Lily

    Interesting idea and nice guide. I tried to implement it for API level 8, but had a few issues such as TLS not recognized as an algorithm for SSLContext.getInstance. Which API version is this for, 14+?

    • Mihal Celovski

      9. May 2012 von Mihal Celovski

      Hi,

      this solution works fine for me. I tried it on phones with API level 7 and 8. You can try this: SSLContext.getInstance(TrustManagerFactory.getDefaultAlgorithm()) or try with KeyManagerFactory.getDefaultAlgorithm().

  • Krzysztof

    18. May 2012 von Krzysztof

    Hi

    Does your method supporting connection reuse as well as SSL session? The idea if to speed up multiple transactions with webservice with just one connection?

    Thx in advance

    • Mihal Celovski

      18. May 2012 von Mihal Celovski

      Hi,

      if you think about reuse of HttpClient, you can make multiple webservice calls through it. SSL session could be set in CustomSslSocketFactory (in example) like this (but I didn’t test it):

      SSLSessionCache sslSession = new SSLSessionCache(context);
      SchemeRegistry registry = new SchemeRegistry();

      registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
      registry.register(new Scheme("https", SSLCertificateSocketFactory.getHttpSocketFactory(10*60*1000, sslSession), 444));

  • Krzysztof

    18. May 2012 von Krzysztof

    Hi,

    I need to make my post more clear – by “reuse” I mean open one TCP connection and one SSL handshake to be done for multiple transaction with https webservice. When the TCP connection is closed (or dropped), session cache can be used to speed up handshake of new TCP connection.

    I`ve checked SSLSessionCache and SSLCertificateSocketFactory with HttpsUrlConnection without any benefit (compared to standard SSLContext.getSocketFactory).

    Have you check connection reuse with tools like tcpdump on server side?

    • Mihal Celovski

      22. May 2012 von Mihal Celovski

      Hi,

      very interesting idea about HttpClient reusing. Honestly, I have never used it in such situations.

      • Krzysztof

        22. May 2012 von Krzysztof

        Nice to hear that.

        I`m new in Java I prefer Linux and ANSI C. Lots on my online related projects was using libcurl there one connection and reuse of handle many times is a norm. The only limitation is to use it in the same thread. I was wondering how to transplant this behavior into Java/Android.

        Yesterday I`ve pushed HttpsUrlConnection to very similar thing. One connection and many requests – it`s working. Today I will dig if sessions are reuse for new connection (eg after timeout). In API14 SSLCertificateSocketFactory has ability to provide custom keyStore and trustStore, as I need to use API10 – I`m feel trouble.

        I will update later…

  • defacto

    Mihal,

    regarding the client certificate: do you think it is possible, instead of a .P12 file, to use a certificate previously installed on the machine via the Security > Settings > Credential storage > Install from SD Card?

    TIA for any suggestions.
    — defacto

    • juanjo

      I think it’s not possible

      Seems that in Android devices the purpose of the certificate storage is only for VPN connections.

      Regards

Comment

Your email address will not be published. Required fields are marked *