Question

I am developing an Android application which requires SSL handshaking to be done only if the server has a particular certificate issued by a CA(For eg: GoDaddy). I referred the documentation on Android developer website but it only says about verifying a self signed certificate or certificate that is not trusted by Android.In my case should I get the client certificate and add it to my keystore.I am using apache HttpClient for my webservice requests. Any help is much appreciated.

Was it helpful?

Solution

It is actually simple. You have to override the checkServerTrusted in your X509TrustManager and throw a CertificateException if the issuer is not GoDaddy. In the code I provided, I used "bla bla", you should probably get the exact name.

You have first to use the provider for your Http Requests: This provider will be used to do the requestes using provider.execute function:

private static AbstractHttpClient provider;
static {

    try {
        BasicHttpParams httpParameters = new BasicHttpParams();
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(null, null);
        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        registry.register(new Scheme("https", new EasySSLSocketFactory(), 443));
        ClientConnectionManager ccm = new ThreadSafeClientConnManager(httpParameters, registry);
        provider = new DefaultHttpClient(ccm, httpParameters);
    } catch (Exception e) {
        e.printStackTrace();
        provider = new DefaultHttpClient();
    }
}

Now you need your EasySSLSocketFactory:

public class EasySSLSocketFactory implements SocketFactory, LayeredSocketFactory 
{  
    private SSLContext sslcontext = null;  

    private static SSLContext createEasySSLContext() throws IOException 
    {  
        try
        {  
            SSLContext context = SSLContext.getInstance("TLS");  
            context.init(null, new TrustManager[] { new EasyX509TrustManager(null) }, null);  
            return context;  
        }
        catch (Exception e) 
        {  
            throw new IOException(e.getMessage());  
        }  
    }  

    private SSLContext getSSLContext() throws IOException 
    {  
        if (this.sslcontext == null) 
        {  
            this.sslcontext = createEasySSLContext();  
        }  
        return this.sslcontext;  
    }  

    /** 
     * @see org.apache.http.conn.scheme.SocketFactory#connectSocket(java.net.Socket, java.lang.String, int, 
     *      java.net.InetAddress, int, org.apache.http.params.HttpParams) 
     */  
    public Socket connectSocket(Socket sock,
            String host,
            int port, 
            InetAddress localAddress,
            int localPort,
            HttpParams params) 

                    throws IOException, UnknownHostException, ConnectTimeoutException 
                    {  
        int connTimeout = HttpConnectionParams.getConnectionTimeout(params);  
        int soTimeout = HttpConnectionParams.getSoTimeout(params);  
        InetSocketAddress remoteAddress = new InetSocketAddress(host, port);  
        SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock : createSocket());  

        if ((localAddress != null) || (localPort > 0)) 
        {  
            // we need to bind explicitly  
            if (localPort < 0) 
            {  
                localPort = 0; // indicates "any"  
            }  
            InetSocketAddress isa = new InetSocketAddress(localAddress, localPort);  
            sslsock.bind(isa);  
        }  

        sslsock.connect(remoteAddress, connTimeout);  
        sslsock.setSoTimeout(soTimeout);  
        return sslsock;    
                    }  

    /** 
     * @see org.apache.http.conn.scheme.SocketFactory#createSocket() 
     */  
    public Socket createSocket() throws IOException {  
        return getSSLContext().getSocketFactory().createSocket();  
    }  

    /** 
     * @see org.apache.http.conn.scheme.SocketFactory#isSecure(java.net.Socket) 
     */  
    public boolean isSecure(Socket socket) throws IllegalArgumentException {  
        return true;  
    }  

    /** 
     * @see org.apache.http.conn.scheme.LayeredSocketFactory#createSocket(java.net.Socket, java.lang.String, int, 
     *      boolean) 
     */  
    public Socket createSocket(Socket socket,
            String host, 
            int port,
            boolean autoClose) throws IOException,  
            UnknownHostException 
            {  
        return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);  
            }  

    // -------------------------------------------------------------------  
    // javadoc in org.apache.http.conn.scheme.SocketFactory says :  
    // Both Object.equals() and Object.hashCode() must be overridden  
    // for the correct operation of some connection managers  
    // -------------------------------------------------------------------  

    public boolean equals(Object obj) {  
        return ((obj != null) && obj.getClass().equals(EasySSLSocketFactory.class));  
    }  

    public int hashCode() {  
        return EasySSLSocketFactory.class.hashCode();  
    }  
}

Finally, and here is the work, you need the EasyX509TrustManager which will not accept except certificates issued by GoDaddy:

public class EasyX509TrustManager implements X509TrustManager 
{  
    private X509TrustManager standardTrustManager = null;  

    /** 
     * Constructor for EasyX509TrustManager. 
     */  
    public EasyX509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException 
    {  
        super();  
        TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());  
        factory.init(keystore);  
        TrustManager[] trustmanagers = factory.getTrustManagers();  
        if (trustmanagers.length == 0) 
        {  
            throw new NoSuchAlgorithmException("no trust manager found");  
        }  
        this.standardTrustManager = (X509TrustManager) trustmanagers[0];  
    }  

    /** 
     * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[],String authType) 
     */  
    public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException 
    {
        standardTrustManager.checkClientTrusted(certificates, authType);  
    }  

    /** 
     * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[],String authType) 
     */  
    public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException 
    {  
        X509Certificate c = certificates[0];
        String name = c.getIssuerDN().getName();
        if(!"bla bla".equals(name))
            throw new CertificateException("OMG! it is not bla bla!");
        standardTrustManager.checkServerTrusted(certificates, authType);    
    }  

    /** 
     * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() 
     */  
    public X509Certificate[] getAcceptedIssuers() 
    {  
        return this.standardTrustManager.getAcceptedIssuers();  
    }    
}  

OTHER TIPS

You need to

  1. Add the peer certificate to your truststore, so you trust it.
  2. Remove the CA certificate from your truststore, so you don't trust any other certificates he has signed.

You can do this easily by:

1. Add the CA Certificate you want to your truststore.
2. Remove all other CA Certificate's(default) from your truststore and catch the SSLHandshakeException.

Or create a new truststore that contains only your CA Certificate.

This is how I check SSL Certs in my code.
I'm validating only CAcert certificats. But you can easily replace the certificate by the root cert from Go Daddy.

It's very easy and safe on API14+ (maybe since 11+).
There is some extra work to make it run on older API levels. Basically I run my own checks on API<14 with the implementation found here: Validate X509 certificates using Java APis

// der formated certificate as byte[]
private static final byte[] CACERTROOTDER = new byte[]{
        48, -126, 7, 61, 48, -126, 5, 37, -96, 3, 2, 1, 2, 2, 1, 0,
        // ...
        };

/**
 * Read x509 certificated file from byte[].
 *
 * @param bytes certificate in der format
 * @return certificate
 */
private static X509Certificate getCertificate(final byte[] bytes)
        throws IOException, CertificateException {
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    X509Certificate ca;
    ByteArrayInputStream is = new ByteArrayInputStream(bytes);
    try {
        ca = (X509Certificate) cf.generateCertificate(is);
        Log.d(TAG, "ca=", ca.getSubjectDN());
    } finally {
        is.close();
    }
    return ca;
}

/**
 * Trust only CAcert's CA. CA cert is injected as byte[]. Following best practices from
 * https://developer.android.com/training/articles/security-ssl.html#UnknownCa
 */
private static void trustCAcert()
        throws KeyStoreException, IOException,
        CertificateException, NoSuchAlgorithmException,
        KeyManagementException {
    // Create a KeyStore containing our trusted CAs
    String keyStoreType = KeyStore.getDefaultType();
    final KeyStore keyStore = KeyStore.getInstance(keyStoreType);
    keyStore.load(null, null);
    keyStore.setCertificateEntry("CAcert-root", getCertificate(CACERTROOTDER));
    // if your HTTPd is not sending the full chain, add class3 cert to the key store
    // keyStore.setCertificateEntry("CAcert-class3", getCertificate(CACERTCLASS3DER));

    // Create a TrustManager that trusts the CAs in our KeyStore
    final TrustManagerFactory tmf = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(keyStore);

    // Create an SSLContext that uses our TrustManager
    SSLContext sslContext = SSLContext.getInstance("TLS");

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        // may work on HC+, but there is no AVD or device to test it
        sslContext.init(null, tmf.getTrustManagers(), null);
    } else {
        // looks like CLR is broken in lower APIs. implement out own checks here :x
        // see https://stackoverflow.com/q/18713966/2331953
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            public boolean verify(final String hostname, final SSLSession session) {
                try {
                    // check if hostname matches DN
                    String dn = session.getPeerCertificateChain()[0].getSubjectDN().toString();

                    Log.d(TAG, "DN=", dn);
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
                        return dn.equals("CN=" + hostname);
                    } else {
                        // no SNI on API<9, but I know the first vhost's hostname
                        return dn.equals("CN=" + hostname)
                                || dn.equals("CN=" + hostname.replace("jsonrpc", "rest"));
                    }
                } catch (Exception e) {
                    Log.e(TAG, "unexpected exception", e);
                    return false;
                }
            }
        });

        // build our own trust manager
        X509TrustManager tm = new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                // nothing to do
                return new X509Certificate[0];
            }

            @Override
            public void checkClientTrusted(final X509Certificate[] chain,
                    final String authType)
                    throws CertificateException {
                // nothing to do
            }

            @Override
            public void checkServerTrusted(final X509Certificate[] chain,
                    final String authType) throws CertificateException {
                // nothing to do
                Log.d(TAG, "checkServerTrusted(", chain, ")");
                X509Certificate cert = chain[0];

                cert.checkValidity();

                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                ArrayList<X509Certificate> list = new ArrayList<X509Certificate>();
                list.add(cert);
                CertPath cp = cf.generateCertPath(list);
                try {
                    PKIXParameters params = new PKIXParameters(keyStore);
                    params.setRevocationEnabled(false); // CLR is broken, remember?
                    CertPathValidator cpv = CertPathValidator
                            .getInstance(CertPathValidator.getDefaultType());
                    cpv.validate(cp, params);
                } catch (KeyStoreException e) {
                    Log.d(TAG, "invalid key store", e);
                    throw new CertificateException(e);
                } catch (InvalidAlgorithmParameterException e) {
                    Log.d(TAG, "invalid algorithm", e);
                    throw new CertificateException(e);
                } catch (NoSuchAlgorithmException e) {
                    Log.d(TAG, "no such algorithm", e);
                    throw new CertificateException(e);
                } catch (CertPathValidatorException e) {
                    Log.d(TAG, "verification failed");
                    throw new CertificateException(e);
                }
                Log.d(TAG, "verification successful");
            }
        };
        sslContext.init(null, new X509TrustManager[]{tm}, null);
    }

    HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
}

I wrote a tiny script to create the byte[] from any given .der file:

#! /bin/sh

if [ $# -lt 1 ] ; then
  echo "usage: $0 file" >&2
  exit 1
fi

echo "private static final byte[] $(echo "$(basename ${1})" | tr -Cd 'a-zA-Z0-9' | tr 'a-z' 'A-Z' ) = new byte[]{"
od -t d1 ${1} | head -n -1  | cut -d\  -f2- | sed -e 's:^  *::' -e 's:  *: :g' -e 's: :, :g' -e 's:$:,:' -e 's:^:    :'
echo "};"

Anyway, you could use a complete trust store with a hand full of trusted certificates if you want to.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top