Monday, July 7, 2014

Make HTTPS Get Request in Android

Making an HTTP get request is pretty simple in Android. However, the problem becomes a little bit tricky when the request is made in HTTPS. If you try to get the response from an HTTPS site by yourself, you may end up with either one of these two scenarios:
a) target site is verified by one of the certificate authorities (CA): you get the correct response you want
b) target site is self-signed: you get an exception - javax.net.ssl.SSLPeerUnverifiedException: No peer certificate

The first scenario actually is no trouble at all, as all you need to do is change "http" to "https", and everything works fine. In fact, for most of the APIs provided by companies such as Facebook and Google, you won't worry about this at all. The real problem is when you want to make an HTTPS request to a self-signed site. As you may want to develop your own site and protect it with HTTPS, you probably won't go through the trouble to verify your certificate with CA. Then you need to make your Android app to make HTTPS request to a self-signed site.

After a bit of searching, this are generally two ways to do this:
a) request with DefaultHttpClient (Apache)
b) request with HttpURLConnection
As Android documentation suggests, DefaultHttpClient has fewer bugs for Android 2.2 and previous versions and HttpURLConnection is the best choice after Android 2.3. So let's just take a look at how to do this with HttpURLConnection.

The very first step is to get a copy of the certificate the target site is using and store it as some_certificate.crt. I understand that getting a certificate can not be automated, which seems tedious, but as HTTPS is meant to secure HTTP, you better to go through this. After you have the certificate file, store it locally on your project, the path I am using is /res/raw/some_certificate.crt.

The second step is to load the certificate in the program. This step actually can be done inline with the HTTPS request, which mean you can load the certificate every time you make the request (but is might slow done your application).

Certificate cert = null; // certificate variable

// you can put this either in a separate function or inline with HTTPS request
try {
 CertificateFactory certFact = CertificateFactory.getInstance("X.509");
 cert = certFact.generateCertificate(context.getResources().openRawResource(R.raw.cert_id));
} catch (Exception e) {
 e.printStackTrace();
 cert = null;
}

The third step is to use the certificate (KeyStore -> TrustManagerFactory -> SSLContext). Notice in this step I revoke the hostname verification.

// load certificate to KeyStore
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(null, null);
keystore.setCertificateEntry("cert_alias", cert);

// initialize a TrustManagerFactory with the KeyStore
TrustManagerFactory tmfact = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmfact.init(keystore);

// initialize SSLContext with TrustManagerFactory
SSLContext sslctx = SSLContext.getInstance("TLS");
sslctx.init(null, tmfact.getTrustManagers(), null);

// this step is just to revoke hostname verification
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){
 @Override
 public boolean verify(String hostname, SSLSession session) {
  return true;
 }
});

// make the  request
URL url = new URL("your url to request with https://");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
r_conn.setSSLSocketFactory(sslctx.getSocketFactory());

Now the connection should be working, and you can read out the input stream of the connection for the real string based response.

// read response
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String r_response = "";
String r_line = null;
while ((r_line = r_reader.readLine()) != null) {
 r_response += r_line;
}

Reference:
1. Android Documentation - HttpURLConnection
2. Android Documentation - HttpsURLConnection
3. StackOverFlow - Android SSL HTTP Request using self signed cert and CA
4. StackOverFlow - java.io.IOException - Hostname was not verified




No comments:

Post a Comment