Bo's blog

Saturday, November 14, 2009

JAVA: How to configure a client for using SSL

 

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)   
        ...   


  • 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)   


  • Quick and Dirty solution



There is a quick and dirty solution: Just tell your client, that you want to accept any certificate, regardless of issuer and host. This can be done by installing a custom TrustManager and a HostnameVerifier. Add the following code to your clients initialization:




    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);   
            }          
        }   
    }   


  • Recommanded Solution:



Needless to say, the quick and dirty solution may is insecure, because it can your requests can be intercepted by a man-in-the-middle attack. Fortunately, there is also a clean solution: Import the servers public key into your truststore.



As a first step, you've got to obtain the servers public key. Assuming, that the key is in your keystore, you may export it by running



    keytool -export -alias tomcat -rfc -file tomcat.crt


This example would export the public key named "tomcat" (which is used by Tomcat) into the file "tomcat.crt". The key would be read from your default keystore, which is the file .keystore in your home directory (something like "c:\Documents and Settings\jwi\.keystore" on windows or "/home/jwi/.keystore" on Linux/Unix).



Obviously, this first step must be done on the server. The second step would be to create a truststore on your client by importing the file "tomcat.crt":



    keytool -import -alias servercert -file tomcat.crt -keystore truststore


The option "-keystore truststore" specifies a file name. Of course, this may as well be an absolute path.

0 Comments:

Post a Comment

<< Home