Tuesday, 4 November 2014

How to connect to an HTTPS web server using self-signed certificates (Android/Java)

Self-signed certificates are quite often used by devs and companies that don't want to pay for a real certificate until is time to go to production.
The Android (Java) HttpsURLConnection will refuse to connect to any web server using a self-signed certificate by default. To get around this, you need replace the default socket factory and make the host name verifier more lenient.
To replace the socket factory, you first need a trust manager. The class below is ready for use
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.X509TrustManager;

public class AllForgivingTrustManager implements X509TrustManager {

 @Override
 public void checkClientTrusted(X509Certificate[] arg0, String arg1)
   throws CertificateException {  
 }

 @Override
 public void checkServerTrusted(X509Certificate[] arg0, String arg1)
   throws CertificateException {  
 }

 @Override
 public X509Certificate[] getAcceptedIssuers() {  
  return new X509Certificate[0];
 }

}
Then you create a socket factory that uses our trust manager
private SSLSocketFactory createSocketFactory() throws KeyManagementException, NoSuchAlgorithmException{
 TrustManager[] trustMan = new TrustManager[] { new AllForgivingTrustManager() };
 SSLContext sslContext = SSLContext.getInstance("TLS");
 sslContext.init(new KeyManager[0], trustMan, new SecureRandom());
 return (SSLSocketFactory) sslContext.getSocketFactory();
}

The last part: create the HTTPS connection. Having a helper method for that can be quite handy. Note the setHostnameVerifier method being called below. Only do that if you want to ignore the host name in the certificate and keep in mind, ignoring the host name can be very dangerous!
private HttpsURLConnection getConnection(URL url) throws IOException{
 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
 // set this only if you intend to POST data
        connection.setDoOutput(true);
 connection.setHostnameVerifier(new AllowAllHostnameVerifier());
 connection.setSSLSocketFactory(createSocketFactory());
 
 return connection;
}
And it's done! You can use all this to retrieve any web page or file. See my previous post How to download a file/web page using HTTP on Android/Java for more details.
public void downloadToFile(String sourceUrl, FileOutputStream destFile) throws IOException{
 URL url = new URL(sourceUrl);
 HttpsURLConnection connection = (HttpsURLConnection) getConnection(url);
 try{
  InputStream connectionStream = new BufferedInputStream(connection.getInputStream());
  try{
   int bytesRead;
   byte[] readBuffer = new byte[1024*32];
   while((bytesRead = connectionStream.read(readBuffer)) > 0){
    destFile.write(readBuffer, 0, bytesRead);
   }
  }finally{
   connectionStream.close();
  }
 }finally{
  connection.disconnect();
 }
}