Wednesday, August 7, 2013

Avoiding "javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated" in HttpClient

When developing an application that uses https, your test server doesn't have a valid SSL certificate. Or sometimes the web site is using a self-signed certificate or the web site is using free SSL certificate. So if you try to connect to the server using Apache HttpClient, you will get a exception telling that the "peer not authenticated". Though it is not a good practice to trust all the certificates in a production software, you may have to do so according to the situation.
This solution resolves the exception caused by "peer not authenticated".

But before we go to the solution, I must warn you that this is not a good idea for a production application. This will violate the purpose of using a security certificate. So unless you have a good reason or if you are sure that this will not cause any problem, don't use this solution.

Normally you create a HttpClient like this.
HttpClient httpclient = new DefaultHttpClient();

But you have to change the way you create the HttpClient.

First you have to create a class extending org.apache.http.conn.ssl.SSLSocketFactory.

import org.apache.http.conn.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

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

public class MySSLSocketFactory extends SSLSocketFactory {
         SSLContext sslContext = SSLContext.getInstance("TLS");

    public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);

        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

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

        sslContext.init(null, new TrustManager[] { tm }, null);
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
    }

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


Then create a method like this.
public HttpClient getNewHttpClient() {
   try {
       KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
       trustStore.load(null, null);

       SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
       sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

       HttpParams params = new BasicHttpParams();
       HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
       HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

       SchemeRegistry registry = new SchemeRegistry();
       registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
       registry.register(new Scheme("https", sf, 443));

       ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

       return new DefaultHttpClient(ccm, params);
   } catch (Exception e) {
       return new DefaultHttpClient();
   }
}

Then you can create the HttpClient.
HttpClient httpclient = getNewHttpClient();

If you are trying to send a post request to a login page the rest of the code would be like this.
private URI url = new URI("url of the action of the form");
HttpPost httppost =  new HttpPost(url);
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();  
nameValuePairs.add(new BasicNameValuePair("username", "user"));  
nameValuePairs.add(new BasicNameValuePair("password", "password"));
try {
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();
InputStream is = entity.getContent();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

You get the html page to the InputStream. Then you can do whatever you want with the returned html page.

But here you will face a problem. If you want to manage a session using cookies, you will not be able to do it with this method. If you want to get the cookies, you will have to do it via a browser. Then only you will receive cookies.

5 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. im getting an error:

    httpclient cannot be resolved.

    public class UserLoginTask extends AsyncTask {
    @Override
    protected Boolean doInBackground(Void... params) {
    // TODO: attempt authentication against a network service.passwordString = mPasswordView.getText().toString();

    ArrayList postParameters = new ArrayList();
    postParameters.add(new BasicNameValuePair("username", mEmailView
    .getText().toString()));
    postParameters.add(new BasicNameValuePair("password",
    md5Hash(passwordString)));

    URI url = new URI(
    "https://apix.pointstreak.com/performance/index.php/testauthenticate/username/password");

    HttpPost httppost = new HttpPost(url);

    try {
    httppost.setEntity(new UrlEncodedFormEntity(postParameters));
    HttpResponse response = httpclient.execute(httppost); // ERROR HERE
    HttpEntity entity = response.getEntity();
    InputStream is = entity.getContent();
    } catch (UnsupportedEncodingException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    return false;
    } catch (ClientProtocolException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    return false;
    } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    return false;
    }
    return true;
    }

    ReplyDelete
    Replies
    1. If you try to make a request without checking the security certificates (which is not a good idea) you have to create the httpclient like this
      HttpClient httpclient = getNewHttpClient();
      All the required methods for this is described above

      Or you can use the default method.
      HttpClient httpclient = new DefaultHttpClient();

      Delete