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;
 }
}

2 comments:

  1. Hi Đức,

    Cảm ơn bạn vì đoạn code rất chi tiết, giúp ích được cho mình rất nhiều.
    Tuy nhiên mình đang rất cần tìm hiểu về SSL trên Android, có thể cho mình email hay Yahoo hoặc Skype để liên lạc được ko?
    Yahoo của mình : homme_ttvn
    email : nguyenkhangcuong@gmail.com

    Cảm ơn rất nhiều.

    ReplyDelete
  2. Hi I happened to come accross your post on Stackoverflow "download-multiple-files-with-a-progress-bar-in-listview-android", I was wondering if you completed building that app and if you might want to share the source code or a link to the repo.
    I am building an app that has a similar feature.
    Also you might want to check out my blogs, I have some interesting articles
    http://xtreme-xploits.blogspot.com/
    Thanks in advance

    ReplyDelete