Saturday, July 24, 2010

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

Please note: this post focuses on the Apache HttpClient library. If you're using plain java, please see this post.

When developing a https application, your test server often doesn't have a (valid) SSL certificate. This will cause the following exception to be thrown when connecting your client to the test server: "javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated".

I will be discussing a way to fix this issue with the apache HttpClient, version 4.0.1 (http://hc.apache.org/httpcomponents-client/).

1. Bits and pieces


You usually create your HttpClient like this:

client = new DefaultHttpClient();

We will need to tell the client to use a different TrustManager. A TrustManager (http://download.oracle.com/docs/cd/E17476_01/javase/1.5.0/docs/api/javax/net/ssl/TrustManager.html) is a class that checks if given credentials (or certificates) are valid. The scheme used by SSL is called X.509 (http://en.wikipedia.org/wiki/X.509), and Java has a specific TrustManager for this scheme, called X509TrustManager. First thing we will need to do is create such a TrustManager:

X509TrustManager tm = new X509TrustManager() {

public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}

public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}

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

As you can see, this code doesn't do much: if a certificate is invalid the TrustManager is supposed to throw a CertificateException in the checkXXX methods. Since we always want to accept all certificates, we never throw an exception.

Next we need to find a way to set this TrustManager in our HttpClient. The TrustManager is used by the SSL sockets. Sockets are created using a SocketFactory. For SSL sockets this is an SSLSocketFactory (http://download.oracle.com/docs/cd/E17476_01/javase/1.5.0/docs/api/javax/net/ssl/SSLSocketFactory.html). When creating a new SSLSocketFactory, you need to pass an SSLContext to the constructor. It is this SSLContext that will contain our newly created TrustManager.

First thing we need to do is get an SSLContext:

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

TLS is the successor to SSL, but they use the same SSLContext.

Then we initialize this context with our new TrustManager that we created above:

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

We can then finally create our SSLSocketFactory:

SSLSocketFactory ssf = new SSLSocketFactory(ctx);

Now we still need to register this SSLSocketFactory with our HttpClient. This is done in the SchemeRegistry of the ConnectionManager of the HttpClient:

ClientConnectionManager ccm = base.getConnectionManager();
SchemeRegistry sr = ccm.getSchemeRegistry();
sr.register(new Scheme("https", ssf, 443));

We register a new Scheme, with the protocol https, our newly created SSLSocketFactory which contains our TrustManager and we tell the HttpClient that the default port for https is port 443.

2. Putting it all together


The following class takes a HttpClient and returns a new HttpClient that accepts any SSL certificate:

/*
This code is public domain: you are free to use, link and/or modify it in any way you want, for all purposes including commercial applications. 
*/
public class WebClientDevWrapper {

public static HttpClient wrapClient(HttpClient base) {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {

public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}

public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}

public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[]{tm}, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx);
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = base.getConnectionManager();
SchemeRegistry sr = ccm.getSchemeRegistry();
sr.register(new Scheme("https", ssf, 443));
return new DefaultHttpClient(ccm, base.getParams());
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
}

You can then do something like this in the code that creates the HttpClient:
this.client = new DefaultHttpClient();
if(dev) {
this.client = WebClientDevWrapper.wrapClient(client);
}

Update

In some exceptional cases, the method described above doesn't work. This is due to the Apache AllowAllHostnameVerifier still being to strict. In this case, you will need your own X509HostnameVerifier. Create it as follows:
X509HostnameVerifier verifier = new X509HostnameVerifier() {

                @Override
                public void verify(String string, SSLSocket ssls) throws IOException {
                }

                @Override
                public void verify(String string, X509Certificate xc) throws SSLException {
                }

                @Override
                public void verify(String string, String[] strings, String[] strings1) throws SSLException {
                }

                @Override
                public boolean verify(String string, SSLSession ssls) {
                    return true;
                }
            };
Then set it on your socket factory:
ssf.setHostnameVerifier(verifier);
If we put everything together, the new code looks like this:
/*
This code is public domain: you are free to use, link and/or modify it in any way you want, for all purposes including commercial applications. 
*/
public class WebClientDevWrapper {

    public static HttpClient wrapClient(HttpClient base) {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {

                public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
                }

                public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
                }

                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            };
            X509HostnameVerifier verifier = new X509HostnameVerifier() {

                @Override
                public void verify(String string, SSLSocket ssls) throws IOException {
                }

                @Override
                public void verify(String string, X509Certificate xc) throws SSLException {
                }

                @Override
                public void verify(String string, String[] strings, String[] strings1) throws SSLException {
                }

                @Override
                public boolean verify(String string, SSLSession ssls) {
                    return true;
                }
            };
            ctx.init(null, new TrustManager[]{tm}, null);
            SSLSocketFactory ssf = new SSLSocketFactory(ctx);
            ssf.setHostnameVerifier(verifier);
            ClientConnectionManager ccm = base.getConnectionManager();
            SchemeRegistry sr = ccm.getSchemeRegistry();
            sr.register(new Scheme("https", ssf, 443));
            return new DefaultHttpClient(ccm, base.getParams());
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
}

You can then do something like this in the code that creates the HttpClient:
this.client = new DefaultHttpClient();
if(dev) {
this.client = WebClientDevWrapper.wrapClient(client);
}

85 comments:

  1. I encountered "Cannot instantiate the type SSLSocketFactory" in the row:

    SSLSocketFactory ssf = new SSLSocketFactory(ctx);

    Thanks for your help~!!

    ReplyDelete
  2. Hi Sharon,

    You are probably trying to instantiate a wrong SSLSocketFactory. The one you need is org.apache.http.conn.ssl.SSLSocketFactory and you probably imported the abstract javax.net.ssl.SSLSocketFactory. Sorry about this, I should have made this more clear in my post.

    Here's an overview of the import statements as they should appear at the top of your class:
    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;
    import org.apache.http.client.HttpClient;
    import org.apache.http.conn.ClientConnectionManager;
    import org.apache.http.conn.scheme.Scheme;
    import org.apache.http.conn.scheme.SchemeRegistry;
    import org.apache.http.conn.ssl.SSLSocketFactory;
    import org.apache.http.impl.client.DefaultHttpClient;

    ReplyDelete
    Replies
    1. i have the same problem: i have imported
      org.apache.http.conn.ssl.SSLSocketFactory
      but i encountred an error because SSlSocketFactory needed keystore as an input.

      Delete
  3. Thanks!
    I don't use Apache HttpClient, but was able to adapt the code because your explanations were very clear.

    ReplyDelete
  4. Nicely explained, and even works great for me.
    Thanks!

    ReplyDelete
  5. Very useful article.

    Thank you very much Mathias.

    ReplyDelete
  6. Hi, Thanks for a very vice article.
    I also had a similar issue when i was trying to use https proxy params.Though
    when i use this piece of code

    ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(
    httpClient.getConnectionManager().getSchemeRegistry(),
    ProxySelector.getDefault());
    ((DefaultHttpClient)httpClient).setRoutePlanner(routePlanner);

    intead of this piece

    HttpHost proxy = new HttpHost(httpsProxyHost,
    Integer.valueOf(httpsProxyPort), "https");
    httpClient.getParams().setParameter(DEFAULT_PROXY, proxy);

    I automatically got routed through proper connection.I wonder if u could help me with it.

    ReplyDelete
  7. Hi Ranjan,

    If you use the ProxySelectorRoutePlanner you will use the default JRE ProxySelector, which looks at the proxy configuration of the operating system. I don't understand what you need help with, since you say your connection works?

    ReplyDelete
  8. [...] If the target server uses self-signed SSL certificates you might want to apply the code in Avoiding the “javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated” with HttpClient. [...]

    ReplyDelete
  9. [...] Avoiding the “javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed” error January 24, 2011 mathiasdegroof Leave a comment Go to comments Please note: this post focuses on the standard Java https implementation. If you are using the Apache HttpClient library, please see this post. [...]

    ReplyDelete
  10. Not sure why it does not work for me. It still gives me the following exception.

    javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
    at com.ibm.jsse2.ec.getPeerCertificates(ec.java:114)
    at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128)
    at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:399)
    at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:143)
    at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:149)
    at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:108)
    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:415)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:641)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:731)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:709)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:700)

    ReplyDelete
  11. Hi Gautam,

    Are you sure you put in the following line: ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);? It also looks like you're using the IBM JSSE. I didn't test the code with the IBM JSSE.

    ReplyDelete
  12. Thank u very much mathiasdegroof!

    Your explain is very clear and useful.

    ReplyDelete
  13. I think you missed one step. Are you sure you put this line in your code:
    ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

    You say you use this in the IPN listener. The IPN listener does a call to the PayPal server, so you shouldn't need to bypass the certificate validation: PayPal will always have a valid SSL certificate. What URL are you calling?

    ReplyDelete
  14. Ok...I am actually testing my IPN Listener againts the PayPal IPN server, (ipn.sanbox.paypal.com:443)...so I am still testing.
    I thought I will be able to bypass the certificate validation by the PayPal IPN sandbox. From what you say, it is unlikely.
    I did this, http://forums.oscommerce.com/topic/166092-paypal-ipn-dummies-guide/, and I got a:
    javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
    Then I tried what you said and I then got:
    javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated.

    Any ideas?

    ReplyDelete
  15. That link you gave is pretty old: it contains posts from 2005. I think the problem is you're using a wrong (old) PayPal url. The URL for the sandbox PayPal IPN is at https://www.sandbox.paypal.com/cgi-bin/webscr. https://ipn.sanbox.paypal.com:443 doesn't exist (anymore?), so that's why you got the first error.
    Please see this article for an example on how to implement an IPN listener (it's a servlet, but you can easily adapt it to struts): http://theskeleton.wordpress.com/2010/07/23/paypal-instant-payment-notification-ipn-servlet-with-httpclient-4/

    ReplyDelete
  16. mathiasde, if you test using the IPN simulator,https://developer.paypal.com/cgi-bin/devscr?cmd=_ipn-link-session and then check your network traffic, you will see that the server, ipn.sanbox.paypal.com is trying to connect send through a notification to your listener, not www.sanbox.paypal.com. The latter IP address is 216.113.191.33 and while the former is 216.113.191.82. The latter is correct.
    I do get my payment correctly from ipn.sanbox.paypal.com and I then get the above error thrown at the line:

    String rc = getRC(client.execute(post)).trim(); as in your IPN link...

    ReplyDelete
  17. It's possible that the request to your server is originating from ipn.sandbox.paypal.com. That doesn't mean that you should use that URL to validate the call. There is no http or https server running on ipn.sandbox.paypal.com (just go to https://ipn.sandbox.paypal.com/ with your web browser), so there is no way your request will succeed. If on the other hand you go to https://www.sandbox.paypal.com, you will see there is a https server running on that address. You don't have to believe me, but you will never get it to work if you use the ipn.sanbox.paypal.com address. The only correct sandbox URL is https://www.sandbox.paypal.com/cgi-bin/webscr

    ReplyDelete
  18. Hi,

    Thanks for your tip. However, I have another question. How do I handle client certificate authentication? I created a CSR and have it signed and returned by the server CA certificate. How do I include this signed CSR in the client side https connection? The whole process worked with Chrome and Firefox. I want to do the same using Httpclient 4.x.

    Thanks.

    ReplyDelete
  19. Hi,

    I've never had to do this myself, but the steps would probably be like this:
    1. Import the key into a local keystore (don't forget to import the trusted CA certificates as well so you have the whole certificate chain in there).
    2. Create a KeyStore object and point this to your local keystore file.
    3. Create a KeyManagerFactory and init this with your KeyStore object and the password to your KeyStore
    4. Call the getKeyManagers() method on the KeyManagerFactory and use those in the init() method of the SSLContext (where it says "null" in the above example)

    Hope this works for your.

    ReplyDelete
  20. Thanks for the prompt reply. With my limited knowledge on this subject, I don't quite understand the purposes of step 1 and 3. For step 1, I don't think the browser (e.g. Chrome) has my trusted CA cert becoz the trusted cert for signing CSR that I loaded in the server is not certified by "Verisign". For step 3, what is the purpose of KeyManager?

    What I did was, I created a self-signed cert (SSC) using keytool. In my code, I loaded the SSC from keystore and get its private and public keys. Using these keys I created a CSR and pass it to the server for signing. When I receive the new certificate from the server I saved it into the keystore. Using the SSLContext with the keystore, I made the SSL connection. As expected, the connection threw the “javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated”.

    I searched the web and trying to look for the process of how a browser handle client side SSL connection. I wonder if you can advice me where to look for more info.

    ReplyDelete
  21. I suppose you're right. Like I said, I've never done this before myself.

    Your code should probably look something like this:

    public class WebClientDevWrapper {

    public static HttpClient wrapClient(HttpClient base) {
    try {
    SSLContext ctx = SSLContext.getInstance("TLS");
    X509TrustManager tm = new X509TrustManager() {

    public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
    }

    public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
    }

    public X509Certificate[] getAcceptedIssuers() {
    return null;
    }
    };
    KeyStore ks = KeyStore.getInstance("jks");
    String keystorePassord = "pass";
    ks.load(new FileInputStream("c:/keystore_file"), keystorePassord.toCharArray());
    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(ks, keystorePassord.toCharArray());
    ctx.init(kmf.getKeyManagers(), new TrustManager[] { tm }, null);
    SSLSocketFactory ssf = new SSLSocketFactory(ctx);
    ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
    ClientConnectionManager ccm = base.getConnectionManager();
    SchemeRegistry sr = ccm.getSchemeRegistry();
    sr.register(new Scheme("https", ssf, 443));
    return new DefaultHttpClient(ccm, base.getParams());
    } catch (Exception ex) {
    ex.printStackTrace();
    return null;
    }
    }
    }

    If you still get errors with that code, please look at the javadoc at the top of the following class: http://svn.apache.org/viewvc/httpcomponents/oac.hc3x/trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/AuthSSLProtocolSocketFactory.java?view=markup which will tell you how to import the correct keys into your keystore.

    Hope this works for you.

    ReplyDelete
  22. I'm glad you got it to work. Thank you for posting your code, I'm sure it will help someone out in the future!

    ReplyDelete
  23. I'm from China.Thanks a lot!

    ReplyDelete
  24. TH Lim,


    setHostnameVerifier is deprecated, can you tell me how will your code change accordingly? I am kind of confused.

    Richard

    ReplyDelete
  25. Actually I got around my earlier question by doing this:
    SSLSocketFactory ssf = new SSLSocketFactory(ctx,SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER );

    instead of ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

    Now I have another problem with httpclient's authscope method. I was thinking it will establish a permanent session with the host that I am specifying but it doesn't seem to. I am not sure how to establish a session to talk to the REST service I am talking to within the same class.

    I get the following error:

    executing request GET https://dam-vchg01.land.virtual/vCenter-CB/api/costModels/ HTTP/1.1
    ----------------------------------------
    HTTP/1.1 200 OK
    Response content length: 318







    Here is my code:
    public class ClientAuthentication {

    public static void main(String[] args) throws Exception {


    DefaultHttpClient httpclient = HttpClientIgnoreInvalidSSLCertificate.createClient();
    //DefaultHttpClient httpclient = new DefaultHttpClient();

    try {

    httpclient.getCredentialsProvider().setCredentials(
    new AuthScope("dam-vchg01.land.virtual", 443),
    new UsernamePasswordCredentials("rjose", "rjose6345"));

    HttpGet httpget = new HttpGet("https://dalm-vchg01.land.virtual/vCenter-CB/api/costModels/");

    System.out.println("executing request" + httpget.getRequestLine());
    HttpResponse response = httpclient.execute(httpget);
    HttpEntity entity = response.getEntity();

    System.out.println("----------------------------------------");
    System.out.println(response.getStatusLine());
    if (entity != null) {
    System.out.println("Response content length: " + entity.getContentLength());

    try {

    BufferedReader reader = new BufferedReader(
    new InputStreamReader(response.getEntity().getContent()));
    String line = "";

    while ((line=reader.readLine())!= null)
    {

    System.out.println(line);
    }

    } catch (IOException ex) {


    ex.printStackTrace();
    throw ex;

    } catch (RuntimeException ex) {


    httpget.abort();
    throw ex;

    } finally {



    }

    }
    EntityUtils.consume(entity);
    } finally {

    httpclient.getConnectionManager().shutdown();
    }
    }
    }



    Please tell me how to get around this. Is there something that i am missing.

    Thanks,
    Richard

    ReplyDelete
  26. Ok I see 3 questions here:

    1. They must have deprecated the setHostnameVerifier() in a recent version, because it wasn't deprecated in the version I used when I wrote the article (v4.0.1, which was the latest version at that time). It's good that you found a workaround.
    2. I don't know what you mean by a "permanent session". There is no such thing in HTTP. Sessions are implemented on the server, either by passing a cookie or a session id parameter in each request. Maybe you mean TCP connection reuse? This is something that HTTPClient will try to do by default (see http://hc.apache.org/httpcomponents-client-ga/tutorial/html/httpagent.html for more info on how to modify this behavior).
    3. You also say you get an error, but I don't see any errors posted, only a HTTP/1.1 200 OK, which means everything went well...

    ReplyDelete
  27. I'm still getting the Peer not authenticated exception, following the code above. Below is the exception and snippet of code I'm using. Any ideas? Thanks much for your help

    javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
    at com.sun.net.ssl.internal.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:352)
    at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128)
    at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:339)
    at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:123)
    at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:147)
    at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:108)
    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:415)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:641)




    public void sendMessage(String url) throws Exception {

    X509TrustManager tm = new X509TrustManager() {
    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
    return null;
    }

    public void checkClientTrusted( java.security.cert.X509Certificate[] chain,
    String authType)
    throws
    java.security.cert.CertificateException {
    // TODO Auto-generated method stub

    }

    public void checkServerTrusted ( java.security.cert.X509Certificate[] chain,
    String authType)
    throws java.security.cert.CertificateException {
    // TODO Auto-generated method stub

    }
    };



    long token = System.currentTimeMillis();

    log.info(TAG + "sendMessage() " + token + " url -> " + url);

    //System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");

    KeyManagerFactory kmf =
    KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(keyStore, KEYSTORE_PASS.toCharArray());

    DefaultHttpClient dhc = new DefaultHttpClient();

    SSLContext sc = SSLContext.getInstance("TLS");
    sc.init(kmf.getKeyManagers(), new TrustManager[]{tm}, null);

    SSLSocketFactory ssf = new SSLSocketFactory(sc);
    ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
    //SSLSocketFactory ssf = new SSLSocketFactory(keyStore, KEYSTORE_PASS, null);
    Scheme s = new Scheme("https", ssf, 443);
    ClientConnectionManager ccm = dhc.getConnectionManager();
    ccm.getSchemeRegistry().register(s);

    HttpGet hp = new HttpGet(url);



    HttpResponse hr = dhc.execute(hp);

    log.info(TAG + "sendMessage() " + token + " response -> " + hr.getStatusLine());
    log.info(TAG + "sendMessage() " + token + " response -> " + hr.getEntity().getContent().toString());

    ccm.shutdown();
    }

    ReplyDelete
  28. Hi Beena,

    I tried your code, except for the KeyManagerFactory, and it works for me. So I assume the server is rejecting your clients certificate. This means it's the client authentication that's failing and not the server authentication. You'll need to make sure that your keystore contains a certificate that is accepted by the server you are connecting to.

    Hope this helps.

    ReplyDelete
  29. Folks, it works!!! Just saved myself a ton of time and effort. Thank you!

    ReplyDelete
  30. Hello,
    Your code was very helpful. My legal department is asking whether you could license this code snippet to us or make some statement about it being public domain. If you attach an Apache 2.0 or BSD style license, that would be easiest.
    Thank you!
    -- Victor

    ReplyDelete
  31. Hi Victor,

    I wouldn't worry too much about it: these licenses are generally used for libraries and this is just a code snippet. Anyone is free to use it in any way they want. I'll add a comment to the class just to make sure.

    ReplyDelete
  32. Hi, and thanks! Even code snippets cannot be used by in a commercial application (at least at our company) unless it's clearly labeled with a license or public domain statement. Thanks so much for your help!

    -- Victor

    ReplyDelete
  33. For those who are still getting "javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated", you may want to turn on ssl debug (-Djavax.net.debug=ssl) to see what the problem is. For my purpose, I actually don't want to trust all certs, so I have trusted root certificates in my truststore. However, I will sometimes still get javax.net.ssl.SSLPeerUnverifiedException for a web server that I've been able to connect to before. Turning on ssl debug reveals that during the handshake, if my app should lose connection to the server due to Connection reset (e.g. java.net.SocketException: Connection reset), it will throw javax.net.ssl.SSLPeerUnverifiedException. So the exception is misleading. The stacktrace (snippet below) shows this is caused by the jre so unfortunately, we will have to wait for Oracle to fix this issue.

    javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
    at com.sun.net.ssl.internal.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:352)
    at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128)
    at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:390)
    at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:148)
    at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:149)
    at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:121)
    at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:562)
    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:415)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:754)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:732)

    SSL debug shows SocketException as real cause:

    Thread-12, WRITE: TLSv1 Handshake, length = 113
    Thread-12, handling exception: java.net.SocketException: Connection reset
    Thread-12, SEND TLSv1 ALERT: fatal, description = unexpected_message
    Thread-12, WRITE: TLSv1 Alert, length = 2
    Thread-12, Exception sending alert: java.net.SocketException: Broken pipe
    Thread-12, called closeSocket()
    Thread-12, called close()
    Thread-12, called closeInternal(true)

    ReplyDelete
  34. Hi i receive this error :
    the constructor SSLSocketFactori(SSLContext) is undefined

    ReplyDelete
  35. Hi TaSSaDaR,

    You are probably trying to instantiate a wrong SSLSocketFactory. The one you need is org.apache.http.conn.ssl.SSLSocketFactory and you probably imported the abstract javax.net.ssl.SSLSocketFactory. Please check the import statements at the top of your class.
    Please note that this article is for the Apache HttpClient library. If you're not using this library, see http://javaskeleton.blogspot.com/2011/01/avoiding-sunsecurityvalidatorvalidatore.html

    ReplyDelete
  36. Is there a way the cert can registered instead of making this code change?

    ReplyDelete
  37. This comment has been removed by the author.

    ReplyDelete
  38. Thanks Mathias! It was very helpful!
    Just a comment:
    The following method

    - sr.register(new Scheme("https", ssf, 443));

    is deprecated (I'm using httpclient 4.1.1)

    It should be

    - sr.register(new Scheme("https", 443, ssf));

    I made a mistake in the previous comment, that's why a deleted it.

    ReplyDelete
  39. In Apache HttpClient version 4.X.X there is much simpler way of doing this (at least for self-signed certificates):
    SSLSocketFactory sslsf = new SSLSocketFactory(new TrustSelfSignedStrategy());
    Scheme https = new Scheme("https", 444, sslsf);
    ccm.getSchemeRegistry().register(https);

    No need to implement you own TrustManager and re-instantiate HttpClient.

    ReplyDelete
  40. Hi Rick,

    Yes, you can add the test certificate you created to the list of trusted certificates on the client. The disadvantage of this approach is that you will need to do this manually on each client that will run your software... This is just for testing, once you go live you will probably want to use a "real" certificate instead of a self-signed one.

    ReplyDelete
  41. Hi,

    The code doesn't work with some URLs like
    https://tradingpartners.comcast.com/PortOut/

    HttpClient 4.1.1. is used. Any idea?

    ReplyDelete
  42. Hi Ahmed,

    It looks like there's a SSL communication error between the host and java for this specific host. You can see this by turning on debug information for the java network system (set the following VM argument: -Djavax.net.debug=all).
    You will see that in the end there is an error in the handshake (Received fatal alert: bad_record_mac). Basically this means the server replied with a format that was not expected by java. This means there is either a bug in the servers ssl implementation or in the java ssl implementation.
    I don't see an easy way to fix this: you could create your own SSLSocketFactory, but that would be quite some work and less than ideal...

    ReplyDelete
  43. Thanks a load buddy... you made my day :)

    ReplyDelete
  44. This comment has been removed by the author.

    ReplyDelete
  45. First of all, thanks a lot for your post, Mathias.

    After following the steps you describe, I was getting the same error. I solved it by instantiating the SSLContext with the String "SSL" instead of "TLS":

    SSLContext ctx = SSLContext.getInstance("SSL");

    I think this is not a perfect solution, but I hope this to be useful to somebody.

    ReplyDelete
  46. i have the same problem in
    SSLContext ctx = SSLContext.getInstance("SSL");

    i tried all the solution but i got nothing...

    ReplyDelete
  47. Hi ciao,

    I have updated the post. Please check the "update" part of the post and apply it to your code. If it still doesn't work, please post the stacktrace.

    ReplyDelete
  48. Hi,

    I get the same error as one of the previous comments....

    The constructor SSLSocketFactory(SSLContext) is undefined

    on the line

    SSLSocketFactory ssf = new SSLSocketFactory(ctx);

    and it recommends changing type of ctx to keystore.

    I've confirmed that the import is:


    import org.apache.http.conn.ssl.SSLSocketFactory;

    It turns out the Android implementation of SSLSocketFactory does not have this constructor. Any way to get around this ?

    ReplyDelete
    Replies
    1. Starting on API level 9 you can use the setDefault method of the SSLContext class:

      http://developer.android.com
      /reference/javax/net/ssl/SSLContext.html#setDefault%28javax.net.ssl.SSLContext%29

      In the example:
      SSLContext.setDefault(ctx);

      Instead of:
      SSLSocketFactory ssf = new SSLSocketFactory(ctx);


      Delete
    2. How to use it then? just replacing the line does not work obviously :) since I need the SSLSocketFactory 2 lines after.

      Delete
  49. I have the same problem, I'm getting this error message:
    The constructor SSLSocketFactory(SSLContext) is undefined

    on this line of code:
    SSLSocketFactory ssf = new SSLSocketFactory(ctx);

    Any suggestions?

    ReplyDelete
  50. Hi,

    I am facing big problem with this HTTPS. I am very new to the field i am not aware of HTTPs more.
    Basically my requirement is i need to send a request server which is running on HTTPs port with some parameters the server will give response as part of response headers. i need to read the headers and do the further process.

    Can you help me to fix the issue. Iam always getting
    javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: error.

    Thanks & Regards,
    kishore

    ReplyDelete
  51. Thank you!! This was very helpful!

    ReplyDelete
  52. Nice explanation. This works fine for httpclient 4.x but how this can be achieved for httpclient 3.x?

    ReplyDelete
  53. Nice...

    I'm using the approach mentioned by Max (to use TrustSelfSignedStrategy class available for Apache HttpClient version 4.X.X)

    ReplyDelete
  54. thanks very much for this article. I made some refactoring ( see http://pastie.org/4316746 ) for:
    * httpclient-4.2 (changed deprecations)
    * no need to "wrap" (no need to re-instantiate an HttpClient)
    * allow to specify https port
    * change the name :-)

    Hope this could help someone else :-)

    ReplyDelete
    Replies
    1. For this line of code:
      SSLSocketFactory ssf = new SSLSocketFactory(ctx, verifier);

      I'm getting this compile error:
      The constructor SSLSocketFactory(SSLContext, X509HostnameVerifier) is undefined

      Any ideas?

      Delete
    2. For HttpClient 4.2.1 I used:

      ...
      import org.apache.http.conn.ssl.AllowAllHostnameVerifier;

      ...

      SSLSocketFactory ssf = new SSLSocketFactory(ctx, new AllowAllHostnameVerifier());

      and I don't get that compiler error.

      Hope this helps.

      Delete
  55. Android API has no constructor SSLSocketFactory(SSLContext sslContext). Has anyone an alternative ?

    ReplyDelete
    Replies
    1. As an "untested workaround" ... Starting on API level 9 you can use the setDefault method of the SSLContext class:

      http://developer.android.com
      /reference/javax/net/ssl/SSLContext.html#setDefault%28javax.net.ssl.SSLContext%29

      In the example:
      SSLContext.setDefault(ctx);

      Instead of:
      SSLSocketFactory ssf = new SSLSocketFactory(ctx);

      Delete
  56. Is this code safe to use in production environments? I ask this because I see an "if (dev)" clause.

    ReplyDelete
  57. Hi John,

    Yes, it is safe to use in production environments, although you normally shouldn't need it in production: you should have a valid certificate on the server, signed by a CA.
    This is why I added the if(dev) clause: in development you often don't have a valid certificate on your server.

    ReplyDelete
  58. If I deploy my war JBOSS AS 7 server, I get below error. If I deploy same war on tomcat 7. it works fine. In both case, my client application is using wrapClient()

    javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake

    ReplyDelete
  59. HOW CAN I USE IT FOR SARDINE
    Sardine sardine = SardineFactory.begin(Username, paswoord);
    WebClientDevWrapper.wrapClient(sardine);

    CAN I USE LIKE THIS. SARDINE IS WEBDAV LIBRARY

    ReplyDelete
  60. can u tell me what does this if(dev) means???

    ReplyDelete
  61. Sunny: this is just a test to see if you're in a development environment. You can leave it out if you don't need it.

    ReplyDelete
    Replies
    1. Hi Mathias,
      after using WebClientDevWrapper class , the speed of execution of code became slow??can u tell me what made so??
      And is there any other ways we can get results for https from java without using code ..(means some setting..)

      Delete
  62. You can simplify the wrapper class as follow (sorry for the poor formatting) :

    public static HttpClient wrapClient(HttpClient base) {
    try {
    ClientConnectionManager ccm = base.getConnectionManager();
    ccm.getSchemeRegistry().register(new Scheme("https", 443, new SSLSocketFactory(new TrustSelfSignedStrategy(), SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)));
    return new DefaultHttpClient(ccm, base.getParams());
    } catch (Exception ex) {
    ex.printStackTrace();
    return null;
    }
    }

    ReplyDelete
  63. I want to use the ALLOW_ALL_HOSTNAME_VERIFIER but I don't want to specify a custom TrustStrategy or a custome SSLContext ... which seems to be required in constructors like the following:

    public SSLSocketFactory(
    final TrustStrategy trustStrategy,
    final X509HostnameVerifier hostnameVerifier)

    public SSLSocketFactory(
    final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier)

    So how do I get around this?

    ReplyDelete
  64. In my particular case, the system time on my device was set in the past.

    Thanks to the page below for pointing out the seemingly obvious... :)


    http://support.pluralsight.com/knowledgebase/articles/47311-i-am-having-trouble-logging-on-to-the-android-app-

    ReplyDelete
  65. The updated example is missing imports, and to save the next person from resolving them all here's what I used:

    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;
    import javax.net.ssl.SSLSocket;
    import org.apache.http.client.HttpClient;
    import org.apache.http.conn.ClientConnectionManager;
    import org.apache.http.conn.scheme.Scheme;
    import org.apache.http.conn.scheme.SchemeRegistry;
    import org.apache.http.conn.ssl.SSLSocketFactory;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.conn.ssl.X509HostnameVerifier;
    import java.io.IOException;
    import javax.net.ssl.*;

    ReplyDelete
  66. Hi!

    SSLSocketFactory ssf = new SSLSocketFactory(ctx, new AllowAllHostnameVerifier()); -> The constructor is undefined

    SSLContext.setDefault(ctx); -> The constructor is undefined for the type SSL

    Any ideas??


    Thanks!

    ReplyDelete
    Replies
    1. Hi Jeff,

      You're probably using a different version of HttpClient than the one I used for this article. If you read trought the comments, you will see that other people had the same problem and posted workarounds already.

      Delete
  67. Thanks for your correct informations,you are providing some good helpful informations. Thanks a lot
    Certificate Authentication

    ReplyDelete
  68. this can be resolved in two ways: the client trust all certificates or server-side add a certificate, the specific cause analysis and solutions see: http://www.trinea.cn/android/android-java-https-ssl-exception-2/

    ReplyDelete
  69. Hi,
    Good details...gives all the info...otherwise getting these details will eat up lot of of time.


    Authentication Certificate

    ReplyDelete