Thursday, February 14, 2013

Android Trusting SSL certificate and Download file using HttpsUrlConnection

The problem which I faced in developing one of application for android want to share with you guys,

My task was to download a .pdf file from a trusted URL in which I need to request to the web server secured with a signed certificate and authenticated URL with some User Name and Password

The problems which I faced step by step are as bellows:-

Problem 1:- I was trying to set up a secure connection via SSL I get a SSL handshake failure when the server requests the certificate.

Solution :- Android: Trusting SSL certificates
  1. Grab all required certificates (root and any intermediate CA’s)
  2. Create a keystore with keytool and the BouncyCastle provider and import the certs
  3. Load the keystore in your android app and use it for the secured connections 
I grabbed my certificate using firefox18.0.2 (Ubuntu 12.04 LTS) in Edit---> Preferences--->Advanced--->Encryption tab ----> View Cerificates --- Inside certificate manager dialog box  ----> servers tab ---->Add Exceptions---> Add security Exception dialog

after pressing "Get Certifcate" button if certificate is valid  then view button will activate by own

Export the certificate from the panel above which will view a file with some extension rename it by "my_certificate.pem"


Create the Keystore

Download the BouncyCastle Provider and store it to a known location.

In Ubuntu I downloaded BouncyCastle using standard command

1- wget http://bouncycastle.org/download/bcprov-jdk16-146.jar

2- keytool -importcert -file my_certificate.pem -keystore yourapp.store -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-jdk16-146.jar -storetype BKS


Put yourapp.store in the “assets” directory of your Android app. Now all you need to do is validate against it. To make a standard HTTPS request:

the block of code need to focus:

AssetManager assetManager = context.getAssets(); 

InputStream keyStoreInputStream = assetManager.open("yourapp.store");  

KeyStore trustStore = KeyStore.getInstance("BKS"); 

trustStore.load(keyStoreInputStream, "somepass".toCharArray());  

TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");

tmf.init(trustStore); 

SSLContext sslContext = SSLContext.getInstance("TLS");  

sslContext.init(null, tmf.getTrustManagers(), null);


 URL url = new URL("url from file have to download");
 HttpsURLConnection ucon = (HttpsURLConnection) url.openConnection();

 ucon.setConnectTimeout(30000);
         ucon.setReadTimeout(30000);
         ucon.setInstanceFollowRedirects(true);

//basic header password witht the Url

//Initially I was getting the FilenotFoundException in Inputstream so need to add basic header authentication with he url request

        String authStr = "UserName" + ":" + "password";
         String authEncoded = Base64.encodeToString(authStr.getBytes(), android.util.Base64.NO_WRAP);
         ucon.setRequestProperty("Authorization", "Basic " + authEncoded);

//My .pdf content type
         ucon.setRequestProperty("Content-Type", "application/octet-stream");
         ucon.setSSLSocketFactory(context.getSocketFactory());

         ucon.setDoInput(true);
         ucon.connect();

InputStream is = ucon.getInputStream();
         FileOutputStream out = new FileOutputStream(outputFile);
         int byteRead = 0;
         byte[] buf = new byte[1024];
         while ((byteRead = is.read(buf)) != -1) {
            out.write(buf, 0, byteRead);
         }
         out.flush();
         out.close();


I created Contentdownloader AsyncTask for secured connection as per my flexibility for an app modify it as per your need.


import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.AsyncTask;
import android.util.Base64;
import android.util.Log;
import android.widget.Toast;
import com.nvison.pdfsignerapp.util.Util;

public class ContentDownloader extends AsyncTask<Boolean, Void, Boolean> {

   private static final String TAG = ContentDownloader.class.getSimpleName();
   private String              directoryLocation;
   private String              downloadURL;
   private String              fileName;
   private String              absolutePath;
   private File                outputFile;
   private ProgressDialog      dialog;
   private Context             context;
   private SSLSocketFactory    sf;

   public ContentDownloader(Context context, String downloadURL, String directoryLocation, String fileName) {
      this.context = context;
      this.downloadURL = downloadURL;
      this.directoryLocation = directoryLocation;
      this.fileName = fileName;
   }

   @Override
   protected void onPreExecute() {
      super.onPreExecute();
      String loadingString = "Downloading pdf...";

      dialog = ProgressDialog.show(context, "", loadingString, true);
   }

   public void download() throws IOException {
      if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {
         outputFile = new File(android.os.Environment.getExternalStorageDirectory(), Util.APPLIACTION_ROOT_FOLDER + File.separator
                  + directoryLocation);
         if (outputFile.exists()) {
            return;
         }
      }
   }

   @Override
   protected Boolean doInBackground(Boolean... params) {

      try {
         download();
         AssetManager assetManager = context.getAssets();
         InputStream keyStoreInputStream = assetManager.open("yourapp.store");
         KeyStore keyStore = KeyStore.getInstance("BKS");
 // password input at a time of generating a keystore using bouncy
         keyStore.load(keyStoreInputStream, "password".toCharArray());
         TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
         tmf.init(keyStore);

         SSLContext context = SSLContext.getInstance("TLS");
         context.init(null, tmf.getTrustManagers(), null);

         File outputFile = new File(Util.getAppRootFolder(), directoryLocation + File.separator + fileName);
         absolutePath = outputFile.getAbsolutePath();
         Log.e(TAG, "Absolute path--" + outputFile.getAbsolutePath());
         Log.e(TAG, "Saving content----" + downloadURL + "---into---" + directoryLocation);
         URL url = new URL(downloadURL);

         HttpsURLConnection ucon = (HttpsURLConnection) url.openConnection();
         // Authenticator.setDefault(new MyAuthenticator());
         ucon.setConnectTimeout(30000);
         ucon.setReadTimeout(30000);
         ucon.setInstanceFollowRedirects(true);
         String authStr = "username" + ":" + "password";
         String authEncoded = Base64.encodeToString(authStr.getBytes(), android.util.Base64.NO_WRAP);
         ucon.setRequestProperty("Authorization", "Basic " + authEncoded);
         ucon.setRequestProperty("Content-Type", "application/octet-stream");
         ucon.setSSLSocketFactory(context.getSocketFactory());

         ucon.setDoInput(true);
         ucon.connect();
         Map<String, List<String>> headerFields = ucon.getHeaderFields();
         int status = ucon.getResponseCode();
         if (status == HttpURLConnection.HTTP_OK) {
            Log.e("TAG", "perfect response code ");
         } else {
            Log.e("TAG", "Wrong response code ");
         }
         InputStream is = ucon.getInputStream();
         FileOutputStream out = new FileOutputStream(outputFile);
         int byteRead = 0;
         byte[] buf = new byte[1024];
         while ((byteRead = is.read(buf)) != -1) {
            out.write(buf, 0, byteRead);
         }
         out.flush();
         out.close();
         return true;
      } catch (Exception e) {

         Log.e(TAG, "Problem while downloading inside doinbackground");
         e.printStackTrace();
      }
      return false;
   }

   @Override
   protected void onPostExecute(Boolean result) {
      if (dialog.isShowing()) {
         dialog.dismiss();
      }
      if (result) {
         Toast.makeText(context, "download result--" + result, Toast.LENGTH_SHORT).show();
      } else {
         Log.e(TAG, "Downloading failed");
      }

   }

}