2 Replies Latest reply: May 31, 2011 9:19 AM by 818985 RSS

    HTTPS connections to a resilient pair of web servers

    818985
      Hi,

      We have a pair of resilient web servers on the internet each with their own separate IP address (two different locations, no shared infrastructure) representing the same web site and set of web services, all requiring https. DNS is configured to round robin the IP addresses, so that the first DNS lookup gives back addr1, addr2 A records then subsequently addr2, addr1 A records. The issue I have is when one web server goes down and how my java web service client code (based on JSSE because its https) behaves. If I browse with IE then it auto-retries if the downed web server is the first A record. Java doesn't appear to though. I suspect it just uses the first IP address returned by InetAddress.getAllByName(host). You can't simply try again because it only goes for the first address every time. Caching then causes issues because while that applciation instance is running every DNS query returns the same result. Is there any way of persuading jsse to try the next dns a record if the first one is a dud?

      Thanks
        • 1. Re: HTTPS connections to a resilient pair of web servers
          handat
          Just catch the exception in a try catch clause when the other server is dead and retry the operation.
          Also make sure you change the java property networkaddress.cache.ttl to a reasonable value so the server name is not cached forever which is the default.
          • 2. Re: HTTPS connections to a resilient pair of web servers
            818985
            Thanks for the reply. I disabled caching altogether at my local Windows machine both within Java as described and also within the OS (Windows DNS client service). However I'm sitting behind a corporate proxy and so there is more caching upstream from my machine to the web site. I've written a test app to connect to a ssl socket and retrieve the certs and it also displays the url versus InetAddress.getAllByName. I can see two IP addresses coming back but the order does not alternate like you would hope - I might get 6 in a row with the same A record ordering, then they switch round. Clearly retrying like you said is not going to give me consistent results and the user might have lose the will to live waiting for something to happen..

            I think JSSE should do this retrying for me by cycling round the getAllByName results if it fails first time - or at least allow me to control which ip adress to go for. Obviously I can't give it an ip address in the first place because then I get handshaking failures.

            For what it is worth here is my code:

            package util;



            import java.io.IOException;
            import java.io.PrintStream;

            import java.net.InetAddress;
            import java.net.URL;
            import java.net.URLConnection;
            import java.net.UnknownHostException;
            import java.security.Security;
            import java.security.cert.Certificate;
            import java.security.cert.X509Certificate;
            import java.util.Calendar;
            import java.util.Date;

            import javax.net.ssl.HttpsURLConnection;
            import javax.net.ssl.SSLSocket;
            import javax.net.ssl.SSLSocketFactory;


            /**
            * <p>CheckHttpsCertificate is a utility that checks the state of a Web server certificate
            * <p>This utility creates a secure connection to the web site then extracts the certificate chain.
            *
            * <p>You can invoke it via a host name/IP address and port number or alternatively via a URL
            *
            * <p>The certificate is retrieved and the expiry date checked. It might be
            * not yet valid, expired, expire today or near expiry - suitable messages are output for each.
            * The warning expiry is by default 30 days but can be changed.</p>
            *
            * <p>The certificate must be trusted by Java - if it is a publicly trusted certificate then
            * issued by e.g. Verisign then this should work automatically.
            *
            * <p>If it is certified under a a private CA then you can import the CA certificate
            * into Java to make it trusted:</p>
            * Under the java home directory (java_home)m there is a file called cacerts
            * (java_home)\lib\security. Run the following commands from a command prompt</p>
            * <code>cd (java_home)\lib\security</code>
            * <code>keytool -keystore java_home\lib\security\cacerts -storepass changeit -import -alias myCA -file mycacert.cer</code>
            * <p> where java_home is the java home,</p>
            * <p> changeit is the (default) cacerts password</p>
            * <p> mycacert.cer is the CA root CA certificat (include the full path)e.</p>
            *
            * <p>See usage for usage information and exit codes.</p>
            * <p>This can be called from a DOS batch file and using ERRORLEVEL to test the exit code.</p>
            * <p>It can also be run from a Unix server</p>
            *
            */
            public class CheckHttpsCertificate {
                 protected SSLSocketFactory _factory;
                 
                 public CheckHttpsCertificate() {
                 Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
                 _factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
                 }
                 
                 public Certificate [] getHttpsCertificate(String host, int port) throws UnknownHostException, IOException {
                      // Create the client socket
                 SSLSocket socket = (SSLSocket)_factory.createSocket(host, port);

                 // Connect to the server
                 socket.startHandshake();
                 // Retrieve the server's certificate chain
                 Certificate[] serverCerts =
                 socket.getSession().getPeerCertificates();
            // Try really really hard to close the socket.
            socket.setSoLinger(false, 0);
            socket.setKeepAlive(false);
            socket.setTcpNoDelay(false);
            socket.close();
                 
                 return serverCerts;
                 }
                 
                 public Certificate [] getHttpsCertificates(String url) throws UnknownHostException, IOException {
                      URL u = new URL(url);
                 URLConnection conn = u.openConnection();
                 conn.connect();
                 if (conn instanceof HttpsURLConnection) {
                           HttpsURLConnection httpsConn = (HttpsURLConnection)conn;
                           Certificate [] certs = httpsConn.getServerCertificates();
                           return certs;
                 }
                 return null;
                 }     

            /**
            * Calculate the number of days left in teh given certificate.
            * @param cert : the certificate to check
            * @return 0: expired or due to expire today, -1: not valid yet, otherwise the number of days left.
            */
            public static int getDaysToExpiry(X509Certificate cert)
            {
                 int daysLeft = 0;
                 
                      Date now = Calendar.getInstance().getTime();
                 
                 Date notBefore = cert.getNotBefore();
                 Date notAfter = cert.getNotAfter();
                 
                 // Make sure that now is between !before and !after dates
                 if (now.before(notBefore)) {
                      daysLeft = -1;
                 }
                 else if (now.after(notAfter))
                 {
                      daysLeft = 0;
                 }
                 else {
                      daysLeft = (int)((notAfter.getTime() - now.getTime()) / (1000L * 60L * 60L * 24L));
                 }
                 
                 return daysLeft;
            }

                 public static void print(PrintStream out, X509Certificate certificate) {
                      out.println("X509:Owner: "
                           + certificate.getSubjectDN().toString()
                           + "\n"
                           + "Issuer: "
                           + certificate.getIssuerDN().toString());
                      out.println(
                           "Serial number: " + certificate.getSerialNumber().toString(10)
                           + "\n"
                           + "Valid from: " + certificate.getNotBefore().toString()
                           + " until: " + certificate.getNotAfter().toString()
                           + "\n");
                 }


                 public static void main(String [] args) {
                      CheckHttpsCertificate chc = new CheckHttpsCertificate();
                      String host = null;
                      String url = null;
                      int port = 443;
                      int warnDays = 30;
                      int retries = 0;
                      System.setProperty("networkaddress.cache.ttl", "0");
                      System.setProperty("networkaddress.cache.negative.ttl", "0");
                      if (args.length == 0) {
                           usage(null);
                      }
                      for (int i = 0; i < args.length; i++) {
                           if (args.equalsIgnoreCase("-host") && i + 1 < args.length) {
                                i++;
                                host = args[i];
                           }
                           else if (args[i].equalsIgnoreCase("-port") && i + 1 < args.length) {
                                i++;
                                try {
                                     port = new Integer(args[i]);
                                } catch (NumberFormatException e) {
                                     usage("Invalid port number " + args[i]);
                                }
                           }
                           else if (args[i].equalsIgnoreCase("-retry") && i + 1 < args.length) {
                                i++;
                                try {
                                     retries = new Integer(args[i]);
                                } catch (NumberFormatException e) {
                                     usage("Invalid retries number " + args[i]);
                                }
                           }
                           else if (args[i].equalsIgnoreCase("-url") && i + 1 < args.length) {
                                i++;
                                url = args[i];
                           }
                           else if (args[i].equalsIgnoreCase("-warn") && i + 1 < args.length) {
                                i++;
                                try {
                                     warnDays = new Integer(args[i]);
                                } catch (NumberFormatException e) {
                                     usage("Invalid warn parameter " + args[i]);
                                }
                           }
                           else if (args[i].equalsIgnoreCase("-?") || args[i].equalsIgnoreCase("-h") || args[i].equalsIgnoreCase("-help")) {
                                usage(null);
                           }
                           else {
                                usage("Unexpected argument " + args[i]);
                           }
                      }

                      if (host == null && url == null) {
                           usage("You must specify a host or URL");
                      }
                      if (host != null && url != null) {
                           usage("You must specify only one of host or URL");
                      }
                      for (;;) {
                 try {
                      Certificate [] certs = null;
                      if (url != null) {
                      certs = chc.getHttpsCertificates(url);
                      if (certs == null || certs.length == 0) {
                           System.err.println("No certificates found at " + url);
                           System.exit(1);
                      }
                      }
                      else {
                      InetAddress [] addrs = InetAddress.getAllByName(host);
                           System.out.print("IP Addresses for " + host + ": ");
                      for (int i = 0; i < addrs.length; i++) {
                           System.out.print(" " + addrs[i].getHostAddress());
                      }
                      System.out.println();
                                     certs = chc.getHttpsCertificate(host, port);
                           if (certs == null || certs.length == 0) {
                                System.err.println("No certificates found at " + host + ":" + port);
                                System.exit(1);
                           }
                      }
                      System.out.println("Number of returned certificates: " + certs.length);
                      System.out.println();
                      for (int i = 0; i < certs.length; i++) {
                           // The server cert is entry 0.
                                if (certs[i] instanceof X509Certificate) {
                                     X509Certificate x509Cert = (X509Certificate) certs[i];
                                     int days = getDaysToExpiry(x509Cert);
                                     if (days == -1) {
                                          System.err.println("ERROR: Certificate not yet valid");
                                          print(System.err, x509Cert);
                                          System.exit(1);
                                     }
                                     else if (days == 0) {
                                System.err.println();
                                          System.err.println("ERROR: Certificate has expired");
                                          print(System.err, x509Cert);
                                          System.exit(1);
                                     }
                                     else if (days <= warnDays) {
                                System.err.println();
                                          System.err.println("WARNING: Certificate near expiry: " + days + " days remaining");
                                          print(System.err, x509Cert);
                                          System.exit(2);
                                     }
                                     else {
                                System.out.println();
                                          System.out.println("INFO: Certificate has " + days + " days remaining");
                                          print(System.err, x509Cert);
                                     }
                                }
                           }
                      System.exit(0);
                 } catch (UnknownHostException e) {
                      retries--;
                 System.err.println("Connection failed: " + e.getMessage());
                 if (retries <= 0) {
                      System.exit(3);
                 }
                 } catch (IOException e) {
                      retries--;
                 System.err.println("Connection failed (IO Exception): " + e.getMessage());
                 if (retries <= 0) {
                      System.exit(3);
                 }
                 }
                      }
            }
                 

            /**
            * <p>Provide some useful key usage:</p>
            * @param explanation - explanation for the usage (canbe null)
            */
            static public void usage(String explanation) {
                 System.err.println(explanation);
                 System.err.println("CheckHttpsCertificate [options]");
                 System.err.println("options:");
                 System.err.println(" -host : host to connect to (IP address or host name / URL");
                 System.err.println(" -port : port number optional (default 443)");
                 System.err.println(" -warn : warning period in days (default 30)");
                 System.err.println(" -h or -? : display this usage");
                 System.err.println(" -retry retryCount: retry connections this many times");
                 
                 System.err.println("Exit code:");
                 System.err.println("0 certificate is valid");
                 System.err.println("1 certificate is near expiry");
                 System.err.println("2 certificate is not yet valid");
                 System.err.println("3 failed to connect");
                 
                 System.exit(0);
            }     

            }

            Edited by: 815982 on May 31, 2011 3:19 PM