Skip to main content

Command Palette

Search for a command to run...

Android and Client Authentication

Updated
4 min read

Recently I have been developing an application that had to support client authentication using certificates. The process wasn’t quite as well documented as I would have expected, so this post aims to help others that need to support client authentication in their application (aimed at Ice Cream Sandwich (ICS) and above).

System Keystore

Traditionally, Android applications created their own keystores for storing sensitive credentials. ICS, however, brought in the ability for applications to access credentials stored in a system keystore when authorised by a user. This not only simplifies the process, but with some devices supporting hardware-backed keystores, it can also be more secure than an application keystore stored in the file system.

If credentials using a particular algorithm are stored using a hardware feature, such as a secure element or Trusted Execution Environment (TEE), they are effectively “bound” to that particular device once installed and so protected against extraction. To determine if an algorithm is hardware-backed, the method KeyChain.isBoundKeyAlgorithm(String) can be used.

Getting an Alias

Before an application can use credentials stored in the system keystore, the user needs to give the application access to them. The KeyChain API provides a simple means of doing this:

KeyChain.choosePrivateKeyAlias (activity, new KeyChainAliasCallback(){
  public void alias(String alias) {
    if(alias != null) {
      // Do something with the selected alias
    } else {
      // User didn't select an alias, do something
    }
  }
}, null, null, null, -1, null);

The user will be shown a dialog where they can select a certificate currently stored in the system keystore, or install a new certificate. When the user selects a certificate and closes the dialog, the callback will be given an alias for the selected certificate that it can use to access it. If the user cancels the dialog, the callback will be given a null alias.

It is important to note that although the KeyChain API allows an application to provide hints as to which certificate the user should choose, it cannot force the user to select a particular certificate.

Once an application has an alias for a certificate, it can be used to obtain information associated with it. Below is an example implementation of an X509KeyManager that uses the alias for a certificate to get it’s certificate chain and private key. This can then be used to initialise an SSL context, before setting a URL connection to use the SSL context to allow it to support client authentication, as shown below. Note that the X509Impl instance should be cached instead of being created for each connection, as getting the certificate chain and private key can be slow.

package com.deftech.spotify;

import java.net.Socket;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509KeyManager;

import android.content.Context;
import android.security.KeyChain;
import android.security.KeyChainException;

public class X509Impl implements X509KeyManager {
    private final String alias;
    private final X509Certificate[] certChain;
    private final PrivateKey privateKey;

    public static SSLContext setForConnection(HttpsURLConnection con, Context context, String alias) throws CertificateException, KeyManagementException {
        SSLContext sslContext = null;
        try {
            sslContext = SSLContext.getInstance("TLS");
        } catch(NoSuchAlgorithmException e){
            throw new RuntimeException("Should not happen...", e);
        }
        sslContext.init(new KeyManager[] { fromAlias(context, alias)}, null, null);
        con.setSSLSocketFactory(sslContext.getSocketFactory());
        return sslContext;
    }

    public static X509Impl fromAlias(Context context, String alias) throws CertificateException {
        X509Certificate[] certChain;
        PrivateKey privateKey;
        try {
            certChain = KeyChain.getCertificateChain(context, alias);
            privateKey = KeyChain.getPrivateKey(context, alias);
        } catch (KeyChainException e) {
            throw new CertificateException(e);
        } catch (InterruptedException e) {
            throw new CertificateException(e);
        }
        if(certChain == null || privateKey == null){
            throw new CertificateException("Can't access certificate from keystore");
        }
        return new X509Impl(alias, certChain, privateKey);
    }

    public X509Impl(String alias, X509Certificate[] certChain, PrivateKey privateKey) throws CertificateException {
        this.alias = alias;
        this.certChain = certChain;
        this.privateKey = privateKey;
    }

    @Override
    public String chooseClientAlias(String[] arg0, Principal[] arg1, Socket arg2) {
        return alias;
    }

    @Override
    public X509Certificate[] getCertificateChain(String alias) {
        if(this.alias.equals(alias)) return certChain;
        return null;
    }

    @Override
    public PrivateKey getPrivateKey(String alias) {
        if(this.alias.equals(alias)) return privateKey;
        return null;
    }

    @Override public final String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
        throw new UnsupportedOperationException();
    }

    @Override public final String[] getClientAliases(String keyType, Principal[] issuers) {
        throw new UnsupportedOperationException();
    }

    @Override public final String[] getServerAliases(String keyType, Principal[] issuers) {
        throw new UnsupportedOperationException();
    }
}

References

android.security.KeyChain

Unifying Key Store Access in ICS

Android Email Client — SSLUtils.java

2 views