July 16, 2009
By design when we open an SSL connection in Java (e.g. through java.net.URL.openConnection(“https://….”)) the JSSE implementation of the SSL protocol performs few validations to ensure the requested host is not fake. This involves validation of the server’s X.509 certificate with the PKIX algorithm and checking the host name agains the certificate subject.
Consider we are trying to download a resource from HTTPS server:
URL url = new URL("https://localhost:8443/");
URLConnection con = url.openConnection();
Reader reader = new InputStreamReader(con.getInputStream());
while (true) {
int ch = reader.read();
if (ch==-1) {
break;
}
System.out.print((char)ch);
}
If the server uses self-signed X.509 certificate, we will get the following exception during the SSL handshaking:
Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Unknown Source) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(Unknown Source) at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Unknown Source) ...
This exception can be avoided if we import the server’s self-signed certificate in the JVM trusted store, a file called “cacerts”. For more information see this post: http://www.java-samples.com/showtutorial.php?tutorialid=210
We could have also another issue. If the server uses trusted certificate (issued from trusted CA like VeriSign), but for different host, we will get another exception during the host verification step of the SSL handshaking:
Exception in thread "main" java.io.IOException: HTTPS hostname wrong: should be <localhost> at sun.net.www.protocol.https.HttpsClient.checkURLSpoofing(Unknown Source) at sun.net.www.protocol.https.HttpsClient.afterConnect(Unknown Source) at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source)
Avoiding these exceptions is possible by switching off the certificate validation and host verification for SSL for the current Java virtual machine. This can be done by replacing the default SSL trust manager and the default SSL hostname verifier:
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
public class Example {
public static void main(String[] args) throws Exception {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
URL url = new URL("https://svn.academy.devbg.org:9024/");
URLConnection con = url.openConnection();
Reader reader = new InputStreamReader(con.getInputStream());
while (true) {
int ch = reader.read();
if (ch==-1) {
break;
}
System.out.print((char)ch);
}
}
}
Voilla! Now the code runs as expected – it downloads the resource from an https address with invalid certificate.
Be careful when using this hack. Skipping certificate validation is dangerous and should be done in testing evironments only.
Tags: Exception, host, java exception, java samples, java ssl, java url, Reader, security, SSL, verification step
Can this code be used in j2me?
Thanks
Comment by Amer — January 29, 2011 @ 02:27
I don’t know. You could test it in Java ME environment, of course.
Comment by nakov — January 30, 2011 @ 18:53
Is this supposed to work when running the client from the command line? I have implemented this but I am still receiving the same error. You have any idea what may be happening?
Comment by kaz — September 15, 2011 @ 06:45
I have no idea. It worked at the time it was written. Now we have newer Java versions that could possibly work differently.
Comment by nakov — September 15, 2011 @ 14:17
It works … I found the issue I was having … it was in an old script that was being used to run the client.
it was setting the command line parameter: -Djava.protocol.handler.pkgs=com.sun.net.ssl.internal.www.protocol … which apparently did not recognize my hostname verifier. Thx for the response.
JFYI … all that is needed when using JAX-WS is:
HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
log.debug( “Warning: URL Host: ” + urlHostName + ” vs. ” + session.getPeerHost() );
return true;
}
} );
Comment by kaz — September 15, 2011 @ 21:05
[...] to a HTTPS server from Java and ignore the validity of the security certificate as well as Disable Certificate Validation in Java SSL Connections, but the accepted answer to the first is for HttpClient 4.0 (unfortunately I cannot upgrade, unless [...]
Pingback by How to make Apache Commons HttpClient 3.1 ignore HTTPS certificate invalidity? | Gravity Layouts — October 7, 2011 @ 11:50
This is very helpful, it worked perfectly. The one change I did was making these two calls on the connection instance (instead of static calls)
httpsConnection.setSSLSocketFactory(sc.getSocketFactory());
httpsConnection.setHostnameVerifier(allHostsValid);
Comment by Madhu — October 27, 2011 @ 19:32
[...] I actually disabled the HTTPS certificate check by using the method described in this nifty blog: http://www.nakov.com/blog/2009/07/16/disable-certificate-validation-in-java-ssl-connections/. In actual production use, however, HTTPS URLs are handled by using the actual certificates [...]
Pingback by How to read the file size of a document served at an HTTP/S URL using Java « Talking with a Lisp — December 4, 2011 @ 20:04
Sweet – just what I needed – thank you. Only thing I needed to add was a permission in my java.policy file like so:
permission javax.net.ssl.SSLPermission “setHostnameVerifier”;
Comment by Mikkel Flindt Heisterberg — January 10, 2012 @ 19:08
[...] przejść do strony z wątpliwym (niezaufanym) certyfikatem SSL – http://www.nakov.com/blog/2009/07/16/disable-certificate-validation-in-java-ssl-connections/ – w [...]
Pingback by Ciekawostki różne « Wiadomości o technologiach IT — January 27, 2012 @ 17:42