Java Tip 96: Use HTTPS in your Java client code

Find out how to use the HTTPS protocol with the standard URL class

If you've ever tried to implement secure communication between a Java client and an HTTPS (HyperText Transfer Protocol Secure) server, you've probably discovered that the standard java.net.URL class doesn't support the HTTPS protocol. The server-side implementation of that equation is fairly straightforward. Almost any Web server available today provides a mechanism for requesting data, using HTTPS. Once you have your Web server set up, any browser can request secure information from your server simply by specifying HTTPS as the protocol for the URL. If you don't already have an HTTPS server set up, you can test your client code with almost any HTTPS Webpage on the Internet. The Resources section contains a short list of candidates that you can use for that purpose.

From the client perspective, however, the simplicity of the S at the end of the familiar HTTP is deceiving. The browser is actually doing a considerable amount of behind-the-scenes work to ensure that no one has tampered with or monitored the information that you requested. As it turns out, the algorithm to do the encryption for HTTPS is patented by RSA Security (for at least a few more months). The use of that algorithm has been licensed by browser manufacturers but was not licensed by Sun Microsystems to be included in the standard Java URL class implementation. As a result, if you attempt to construct a URL object with a string specifying HTTPS as the protocol, a MalformedURLException will be thrown.

Fortunately, to accommodate that constraint, the Java specification provides for the ability to select an alternate stream handler for the URL class. However, the technique required to implement that is different, depending on the virtual machine (VM) you use. For Microsoft's JDK 1.1-compatible VM, JView, Microsoft has licensed the algorithm and provided an HTTPS stream handler as part of its wininet package. Sun, on the other hand, has recently released the Java Secure Sockets Extension (JSSE) for JDK 1.2-compatible VMs, in which Sun has also licensed and provided an HTTPS stream handler. This article will demonstrate how to implement the use of an HTTPS-enabled stream handler, using the JSSE and Microsoft's wininet package.

JDK 1.2-compatible virtual machines

The technique for using JDK 1.2-compatible VMs relies primarily on the Java Secure Sockets Extension (JSSE) 1.0.1. Before that technique will work, you must install the JSSE and add it to the class path of the client VM in question.

After you have installed the JSSE, you must set a system property and add a new security provider to the Security class object. There are a variety of ways to do both of these things, but for the purposes of this article, the programmatic method is shown:

   System.setProperty("java.protocol.handler.pkgs",
        "com.sun.net.ssl.internal.www.protocol");
   Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());

After making the previous two method calls, the MalformedURLException will no longer be thrown by calling the following code:

   URL url = new URL("https://[your server]");

If you are connecting to the standard SSL port, 443, you have the option of appending the port number to the URL string. However, if your Web server is using a nonstandard port for SSL traffic, you'll need to append the port number to your URL string like this:

   URL url = new URL("https://[your server]:7002");

One caveat of that technique concerns a URL that refers to a server that has an unsigned or invalid SSL certificate. In that case an attempt to retrieve the input or output stream from the URL's connection object will throw an SSLException with the message "untrusted server cert chain." If the server has a valid, signed certificate, no exception will be thrown.

   URL url = new URL("https://[your server]");
   URLConnection con = URL.openConnection();
   //SSLException thrown here if server certificate is invalid
   con.getInputStream();

The obvious solution to that problem is to get signed certificates for your server. However, one of the following URLs may also provide a solution: "Java Secure Socket Extension 1.0.2 Changes" (Sun Microsystems) or Sun's Java Developer Connection forum.

Microsoft JView

Due in part to the ongoing dispute between Microsoft and Sun over the licensing of Java for use on Windows platforms, the Microsoft JView VM is currently only JDK 1.1-compliant. Therefore, the technique described above will not work for clients running in JView, as the JSSE requires at least a 1.2.2-compatible VM. Conveniently enough, however, Microsoft provides an HTTPS-enabled stream handler as part of the com.ms.net.wininet package.

You can set the stream handler in a JView environment by calling a single static method on the URL class:

    URL.setURLStreamHandlerFactory(new
    com.ms.net.wininet.WininetStreamHandlerFactory());

After making the previous method call, the

MalformedURLException

will no longer be thrown by calling the following code:

    URL url = new URL("https://[your server]");

There are two caveats associated with that technique. First, according to the JDK documentation, the setURLStreamHandlerFactory method may be called at most once in a given VM. Subsequent attempts to call that method will throw an Error. Second, as is the case with the 1.2 VM solution, you must be cautious when using a URL that refers to a server with an unsigned or invalid SSL certificate. As with the previous case, problems occur when an attempt is made to retrieve the input or output stream from the URL's connection object. However, instead of throwing an SSLException, the Microsoft stream handler throws a standard IOException.

    URL url = new URL("https://[your server]");
    URLConnection con = url.openConnection();
    //IOException thrown here if server certificate is invalid
    con.getInputStream();

Again, the obvious solution to that problem is to attempt HTTPS communication only with servers that have a signed, valid certificate. However, JView offers one other option. Immediately prior to retrieving the input or output stream from the URL's connection object, you can call setAllowUserInteraction(true) on the connection object. That will cause JView to display a message warning the user that the server's certificates are invalid, but giving him or her the option to proceed anyway. Keep in mind, however, that such messages may be reasonable for a desktop application, but having dialog boxes appear on your server for anything other than debugging purposes is probably unacceptable.

Note: You can also call the setAllowUserInteraction() method in JDK 1.2-compatible VMs. However, in using Sun's 1.2 VM (with which this code was tested), no dialogs are displayed even when that property is set to true.

    URL url = new URL("https://[your server]");
    URLConnection con = url.openConnection();
    //causes the VM to display a dialog when connecting
    //to untrusted servers
    con.setAllowUserInteraction(true);
    con.getInputStream();

The com.ms.net.wininet package appears to be installed and placed on the system classpath by default on Windows NT 4.0, Windows 2000, and Windows 9x systems. Also, according to the Microsoft JDK documentation, WinInetStreamHandlerFactory is "...the same handler that is installed by default when running applets."

Platform independence

Although both of those techniques I've described cover most of the platforms on which your Java client may run, your Java client may need to run on both JDK 1.1- and JDK 1.2-compliant VMs. "Write once, run anywhere," remember? As it turns out, combining those two techniques so that the appropriate handler is loaded depending on the VM, is fairly straightforward. The following code demonstrates one way to go about that:

  String strVendor = System.getProperty("java.vendor");
  String strVersion = System.getProperty("java.version");
  //Assumes a system version string of the form:
  //[major].[minor].[release]  (eg. 1.2.2)
  Double dVersion = new Double(strVersion.substring(0, 3));
  //If we are running in a MS environment, use the MS stream handler.
  if( -1 < strVendor.indexOf("Microsoft") )
  {
    try
    {
      Class clsFactory = 
        Class.forName("com.ms.net.wininet.WininetStreamHandlerFactory" );
      if ( null != clsFactory )
        URL.setURLStreamHandlerFactory(
         (URLStreamHandlerFactory)clsFactory.newInstance());
    }
    catch( ClassNotFoundException cfe )
    {
      throw new Exception("Unable to load the Microsoft SSL " +
        "stream handler.  Check classpath."  +  cfe.toString());
    }
    //If the stream handler factory has 
    //already been successfully set
    //make sure our flag is set and eat the error
    catch( Error err ){m_bStreamHandlerSet = true;}
  }
  //If we are in a normal Java environment,
  //try to use the JSSE handler.
  //NOTE:  JSSE requires 1.2 or better
  else if( 1.2 <= dVersion.doubleValue() )
  {
    System.setProperty("java.protocol.handler.pkgs", 
      "com.sun.net.ssl.internal.www.protocol"); 
    try
    {
    //if we have the JSSE provider available, 
    //and it has not already been
    //set, add it as a new provide to the Security class.
    Class clsFactory = Class.forName("com.sun.net.ssl.internal.ssl.Provider");
    if( (null != clsFactory) && (null == Security.getProvider("SunJSSE")) )
        Security.addProvider((Provider)clsFactory.newInstance());
    }
    catch( ClassNotFoundException cfe )
    {
      throw new Exception("Unable to load the JSSE SSL stream handler." +  
        "Check classpath."  + cfe.toString());
    }
  }

What about applets?

Performing HTTPS-based communication from within an applet seems like a natural extension of scenarios described above. In reality, it's even easier in most cases. In 4.0 and later versions of Netscape Navigator and Internet Explorer, HTTPS is enabled by default for their respective VMs. Therefore, if you want to create an HTTPS connection from within your applet code, simply specify HTTPS as your protocol when creating an instance of the URL class:

   URL url = new URL("https://[your server]");

If the client browser is running Sun's Java 2 plug-in, then there are additional limitations to how you can use HTTPS. A full discussion on using HTTPS with the Java 2 plug-in can be found on Sun's Website (see Resources).

Conclusion

Using the HTTPS protocol between applications can be a quick and effective way to gain a reasonable level of security in your communication. Unfortunately, the reasons that it is not supported as part of the standard Java specification appear to be more legal than technical. However, with the advent of the JSSE and the use of Microsoft's com.ms.net.winint package, secure communication is possible from most platforms with only a few lines of code.

Matt Towers, a self-described eBozo, recently left his development position with Visio. He's since joined an Internet startup, PredictPoint.com, in Seattle, Wash., where he's working as a full-time Java developer.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies