Thursday 12 July 2012

Android: Trusting SSL certificates - Exchange Web Service



Previous post from crazybob doesn't work for me. The problem is I want to authenticate Exchange Web Service from Android.  And the long story has begun.

You will need the following tools:





Use the following command to create keystore:

keytool -importcert -v -trustcacerts -file "YourCerFile.cer" -al

ias parkgroup_restful -keystore "Output.bks" -provider org.bounc

ycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar

" -storetype BKS -storepass "YourPass"
 It should show some result, then ask (choose yes):

Trust this certificate? [no]: yes
 Then verify if the certificates were imported correctly into the keystore:

keytool -list -keystore  "Output.bks"  -provi

der org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar" -storetype BKS -storepass "YourPass"
If success, result will be show like this:

Keystore type: BKS

Keystore provider: BC

Your keystore contains 1 entry

parkgroup_restful, Apr 10, 2012, trustedCertEntry,

Certificate fingerprint (MD5): 36:47:88:62:23:1C:F3:52:17:BE:7A:A9:94:56:19:18


Use keystore in your app.

Create custom trust manager

import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import android.util.Log;

/**
 * Manage trust certificate
 *
 * @author ductran
 *
 */
public class CustomTrustManager implements X509TrustManager
{
   
 private static final String TAG = CustomTrustManager.class.getSimpleName();

 static class LocalStoreX509TrustManager implements X509TrustManager
 {

  private X509TrustManager trustManager;

  LocalStoreX509TrustManager(KeyStore localTrustStore)
  {
   try
   {
    TrustManagerFactory tmf = TrustManagerFactory
      .getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(localTrustStore);

    trustManager = findX509TrustManager(tmf);
    if (trustManager == null)
    {
     throw new IllegalStateException("Couldn't find X509TrustManager");
    }
   } catch (GeneralSecurityException e)
   {
    throw new RuntimeException(e);
   }

  }

  @Override
  public void checkClientTrusted(X509Certificate[] chain, String authType)
    throws CertificateException
  {
   trustManager.checkClientTrusted(chain, authType);
  }

  @Override
  public void checkServerTrusted(X509Certificate[] chain, String authType)
    throws CertificateException
  {
   trustManager.checkServerTrusted(chain, authType);
  }

  @Override
  public X509Certificate[] getAcceptedIssuers()
  {
   return trustManager.getAcceptedIssuers();
  }
 }

 static X509TrustManager findX509TrustManager(TrustManagerFactory tmf)
 {
  TrustManager tms[] = tmf.getTrustManagers();
  for (int i = 0; i < tms.length; i++)
  {
   if (tms[i] instanceof X509TrustManager)
   {
    return (X509TrustManager) tms[i];
   }
  }

  return null;
 }

 private X509TrustManager defaultTrustManager;
 private X509TrustManager localTrustManager;

 private X509Certificate[] acceptedIssuers;

 public CustomTrustManager(KeyStore localKeyStore)
 {
  try
  {
   TrustManagerFactory tmf = TrustManagerFactory
     .getInstance(TrustManagerFactory.getDefaultAlgorithm());
   tmf.init((KeyStore) null);

   defaultTrustManager = findX509TrustManager(tmf);
   if (defaultTrustManager == null)
   {
    throw new IllegalStateException("Couldn't find X509TrustManager");
   }

   localTrustManager = new LocalStoreX509TrustManager(localKeyStore);

   List<x509certificate> allIssuers = new ArrayList<x509certificate>();
   for (X509Certificate cert : defaultTrustManager.getAcceptedIssuers())
   {
    allIssuers.add(cert);
   }
   for (X509Certificate cert : localTrustManager.getAcceptedIssuers())
   {
    allIssuers.add(cert);
   }
   acceptedIssuers = allIssuers.toArray(new X509Certificate[allIssuers
     .size()]);
  } catch (GeneralSecurityException e)
  {
   throw new RuntimeException(e);
  }

 }

 public void checkClientTrusted(X509Certificate[] chain, String authType)
   throws CertificateException
 {
  try
  {
   Log.d(TAG, "checkServerTrusted() with default trust manager...");
   defaultTrustManager.checkClientTrusted(chain, authType);
  } catch (CertificateException ce)
  {
   Log.d(TAG, "checkServerTrusted() with local trust manager...");
   localTrustManager.checkClientTrusted(chain, authType);
  }
 }

 public void checkServerTrusted(X509Certificate[] chain, String authType)
   throws CertificateException
 {
  try
  {
   Log.d(TAG, "checkServerTrusted() with default trust manager...");
   defaultTrustManager.checkServerTrusted(chain, authType);
  } catch (CertificateException ce)
  {
   Log.d(TAG, "checkServerTrusted() with local trust manager...");
   localTrustManager.checkServerTrusted(chain, authType);
  }
 }

 public X509Certificate[] getAcceptedIssuers()
 {
  return acceptedIssuers;
 }

}

Create MySSLSocketFactory






import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.scheme.LayeredSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

/**
 *  loosely based on org.apache.http.conn.ssl.SSLSocketFactory
 * @author ductran
 *
 */
public class MySSLSocketFactory implements LayeredSocketFactory {

   // private SSLContext sslCtx;
    private SSLSocketFactory socketFactory;
    private X509HostnameVerifier hostnameVerifier;

    public MySSLSocketFactory(SSLContext sslCtx,
            X509HostnameVerifier hostnameVerifier) {
        //this.setSslCtx(sslCtx);
        this.socketFactory = sslCtx.getSocketFactory();
        this.hostnameVerifier = hostnameVerifier;
    }

    @Override
    public Socket connectSocket(Socket sock, String host, int port,
            InetAddress localAddress, int localPort, HttpParams params)
            throws IOException, UnknownHostException, ConnectTimeoutException {
        if (host == null) {
            throw new IllegalArgumentException("Target host may not be null.");
        }
        if (params == null) {
            throw new IllegalArgumentException("Parameters may not be null.");
        }

        SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock : createSocket());

        if ((localAddress != null) || (localPort > 0)) {
            if (localPort < 0)
                localPort = 0;

            InetSocketAddress isa = new InetSocketAddress(localAddress,
                    localPort);
            sslsock.bind(isa);
        }

        int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
        int soTimeout = HttpConnectionParams.getSoTimeout(params);

        InetSocketAddress remoteAddress = new InetSocketAddress(host, port);

        sslsock.connect(remoteAddress, connTimeout);

        sslsock.setSoTimeout(soTimeout);
        try {
            hostnameVerifier.verify(host, sslsock);
        } catch (IOException iox) {
            try {
                sslsock.close();
            } catch (Exception x) {
            }

            throw iox;
        }

        return sslsock;
    }

    @Override
    public Socket createSocket() throws IOException {
        return socketFactory.createSocket();
    }

    @Override
    public boolean isSecure(Socket sock) throws IllegalArgumentException {
        if (sock == null) {
            throw new IllegalArgumentException("Socket may not be null.");
        }

        if (!(sock instanceof SSLSocket)) {
            throw new IllegalArgumentException(
                    "Socket not created by this factory.");
        }

        if (sock.isClosed()) {
            throw new IllegalArgumentException("Socket is closed.");
        }
        return true;

    }
    @Override
    public Socket createSocket(Socket socket, String host, int port,
            boolean autoClose) throws IOException, UnknownHostException {
        SSLSocket sslSocket = (SSLSocket) socketFactory.createSocket(socket,
                host, port, autoClose);
        hostnameVerifier.verify(host, sslSocket);

        return sslSocket;
    }
}
And make request with HttpsClient:


public class HttpsClient
{
    private static final String KEYSTORE_PASSWORD = "1234567";
    private static final int MAX_CONN_PER_ROUTE = 30;
    private static final int MAX_CONNECTIONS = 30;
    private static final int TIMEOUT = 10 * 1000;
    private static DefaultHttpClient theClient = null;
    private HttpResponse  response = null;
    private static DefaultHttpClient getHttpClient() throws GeneralSecurityException{
  if (theClient == null)
  {
   SSLContext sslContext = createSslContext();
   MySSLSocketFactory socketFactory = new MySSLSocketFactory(sslContext,
     new BrowserCompatHostnameVerifier());
   theClient = createDefaultHttpClient(socketFactory);
  }
  return theClient;
 }

  public HttpResponse  getResponse(){
    return response;
 }

  /**
  * Excute the request
  */
  public void excuteRequest(String url, String username, String password, String requestBody){
  DefaultHttpClient client = getHttpClient();
  HttpPost httpPost = new HttpPost(url);
  httpPost.setHeader("Content-type", "text/xml; charset=utf-8");
  httpPost.setHeader("Keep-Alive", "300");
  httpPost.setHeader("Connection", "Keep-Alive");
  StringEntity se = new StringEntity(requestBody, "UTF-8");
  httpPost.setEntity(se);
  se.setContentType("text/xml");
  se.setContentEncoding("gzip,deflate");
  // Authentication
  client.addRequestInterceptor(preemptiveAuth, 0);
  // Set the proxy
  ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(
    client.getConnectionManager().getSchemeRegistry(),
    ProxySelector.getDefault());
  client.setRoutePlanner(routePlanner);

  List<string> authPrefs = new ArrayList<string>();
  authPrefs.add(AuthPolicy.DIGEST);
  authPrefs.add(AuthPolicy.BASIC);
  authPrefs.add(AuthPolicy.NTLM);
  client.getParams().setParameter(HTTP_AUTH_SCHEME_PRIORITY, authPrefs);
             
                // Basic authentication
  httpPost.addHeader(
    "Authorization",
    "Basic "
      + Base64.encodeToString(
        (username() + ":" + password())
          .getBytes(), Base64.NO_WRAP));
  BasicHttpContext localContext = new BasicHttpContext();
  BasicScheme basicAuth = new BasicScheme();
  localContext.setAttribute("preemptive-auth", basicAuth);
  response = client.execute(httpPost, localContext);
           
     }

  /**
  * Authentication with preemptive
  */
 HttpRequestInterceptor preemptiveAuth = new HttpRequestInterceptor()
 {
  public void process(final HttpRequest request, final HttpContext context)
    throws HttpException, IOException
  {
   AuthState authState = (AuthState) context
     .getAttribute(ClientContext.TARGET_AUTH_STATE);
   CredentialsProvider credsProvider = (CredentialsProvider) context
     .getAttribute(ClientContext.CREDS_PROVIDER);
   HttpHost targetHost = (HttpHost) context
     .getAttribute(ExecutionContext.HTTP_TARGET_HOST);

   if (authState.getAuthScheme() == null)
   {
    AuthScope authScope = new AuthScope(targetHost.getHostName(),
      targetHost.getPort());
    Credentials creds = credsProvider.getCredentials(authScope);
    if (creds != null)
    {
     authState.setAuthScheme(new BasicScheme());
     authState.setCredentials(creds);
    }
   }
  }
 };

          private static DefaultHttpClient createDefaultHttpClient(
   SocketFactory socketFactory)
 {
  HttpParams params = new BasicHttpParams();
  HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
  // HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
  HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
  // Set the parameters for timeout, max connection
  HttpConnectionParams.setConnectionTimeout(params, TIMEOUT);
  ConnPerRoute connPerRoute = new ConnPerRouteBean(MAX_CONN_PER_ROUTE);
  ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
  ConnManagerParams.setMaxTotalConnections(params, MAX_CONNECTIONS);

  // Register the scheme for Http and Https
  SchemeRegistry schemeRegistry = new SchemeRegistry();
  schemeRegistry.register(new Scheme("http", PlainSocketFactory
    .getSocketFactory(), 80));
  SocketFactory sslSocketFactory = SSLSocketFactory.getSocketFactory();
  if (socketFactory != null)
  {
   sslSocketFactory = socketFactory;
  }
  schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));
  ClientConnectionManager cm = new ThreadSafeClientConnManager(params,
    schemeRegistry);
  return new DefaultHttpClient(cm, params);
 }


        /**
  * Create SSL Context from X509TrustManager
  *
  * @param clientAuth
  * @return
  * @throws GeneralSecurityException
  */
 private static SSLContext createSslContext() throws GeneralSecurityException
 {
  KeyStore trustStore = loadTrustStore();
  KeyStore keyStore = loadKeyStore();

  // CustomTrustManager myTrustManager = new CustomTrustManager(trustStore);
  EwsX509TrustManager myTrustManager = new EwsX509TrustManager(trustStore,
    null);
  TrustManager[] tms = new TrustManager[]
  { myTrustManager };

  KeyManager[] kms = null;
  KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory
    .getDefaultAlgorithm());
  kmf.init(keyStore, KEYSTORE_PASSWORD.toCharArray());
  kms = kmf.getKeyManagers();

  SSLContext context = SSLContext.getInstance("TLS");
  context.init(kms, tms, null);

  return context;
 }
}