Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Java Tip 111: Implement HTTPS tunneling with JSSE

Create your own HTTPS tunneling socket for your Java Secure Socket Extension application

  • Print
  • Feedback
The Java Secure Socket Extension (JSSE) library from Sun Microsystems lets you access a secure Web server from behind a firewall via proxy tunneling. To do this, the JSSE application needs to set the https.ProxyHost and https.ProxyPort system properties. The tunneling code in JSSE checks for "HTTP 1.0" in the proxy's response. If your proxy, like many, returns "HTTP 1.1", you will get an IOException. In this case, you need to implement your own HTTPS tunneling protocol.

In this article, I will show you how to create a secure socket that tunnels through the firewall, and pass it to the HTTPS stream handler to open HTTPS URLs using the URLConnection class.

Open the http tunnel socket to the proxy

The first step to creating your secure socket is to open the tunneling socket to the proxy port. The code needed to do this proxy handshaking can be found in the sample code SSLClientSocketWithTunneling.java that comes with the JSSE distribution. First, a normal socket is created that connects to the proxy port on the proxy host (line 65). After the socket is created, it is passed to the doTunnelHandshake() method where the proxy's tunneling protocol is called:

54         SSLSocketFactory factory =
55                  (SSLSocketFactory)SSLSocketFactory.getDefault();
56         
57         /*
58         * Set up a socket to do tunneling through the proxy.
59         * Start it off as a regular socket, then layer SSL
60         * over the top of it.
61         */
62         tunnelHost = System.getProperty("https.proxyHost");
63         tunnelPort = Integer.getInteger("https.proxyPort").intValue();
64         
65         Socket tunnel = new Socket(tunnelHost, tunnelPort);
66         doTunnelHandshake(tunnel, host, port);


In doTunnelHandshake(), an http "CONNECT" command is sent to the proxy, with the secure site's hostname and port number as the parameters (line 161). In the original tunneling code on line 206 in JSSE, it then checks for "HTTP/1.0 200" in the proxy's reply. If your organization's proxy replies with "HTTP 1.1", an IOException will be thrown. To get around this, the code here checks for the reply "200 Connection Established", which indicates that tunneling is successful (line 207). You can modify the code to check for the expected corresponding response from your proxy:

139   private void doTunnelHandshake(Socket tunnel, String host, int port)
140                                 throws IOException
141   {
142      OutputStream out = tunnel.getOutputStream();
143      String msg = "CONNECT " + host + ":" + port + " HTTP/1.0\n"
144                  + "User-Agent: "
145                  + sun.net.www.protocol.http.HttpURLConnection.userAgent
146                  + "\r\n\r\n";
147      byte b[];
148      try {
149         /*
150         * We really do want ASCII7 -- the http protocol doesn't change
151         * with locale.
152         */
153         b = msg.getBytes("ASCII7");
154      } catch (UnsupportedEncodingException ignored) {
155         /*
156         * If ASCII7 isn't there, something serious is wrong, but
157         * Paranoia Is Good (tm)
158         */
159         b = msg.getBytes();
160      }
161      out.write(b);
162      out.flush();
163      
164      /*
165      * We need to store the reply so we can create a detailed
166      * error message to the user.
167      */
168      byte           reply[] = new byte[200];
169      int            replyLen = 0;
170      int            newlinesSeen = 0;
171      boolean        headerDone = false;     /* Done on first newline */
172      
173      InputStream    in = tunnel.getInputStream();
174      boolean        error = false;
175      
176      while (newlinesSeen < 2) {
177         int i = in.read();
178         if (i < 0) {
179            throw new IOException("Unexpected EOF from proxy");
180         }
181         if (i == '\n') {
182            headerDone = true;
183            ++newlinesSeen;
184         } else if (i != '\r') {
185            newlinesSeen = 0;
186            if (!headerDone && replyLen < reply.length) {
187               reply[replyLen++] = (byte) i;
188            }
189         }
190      }
191      
192      /*
193      * Converting the byte array to a string is slightly wasteful
194      * in the case where the connection was successful, but it's
195      * insignificant compared to the network overhead.
196      */
197      String replyStr;
198      try {
199         replyStr = new String(reply, 0, replyLen, "ASCII7");
200      } catch (UnsupportedEncodingException ignored) {
201         replyStr = new String(reply, 0, replyLen);
202      }
203      
204      /* We check for Connection Established because our proxy returns 
205       * HTTP/1.1 instead of 1.0 */
206      //if (!replyStr.startsWith("HTTP/1.0 200")) {
207      if(replyStr.toLowerCase().indexOf(
208                                    "200 connection established") == -1){
209         throw new IOException("Unable to tunnel through "
210                              + tunnelHost + ":" + tunnelPort
211                              + ".  Proxy returns \"" + replyStr + "\"");
212      }
213      
214      /* tunneling Handshake was successful! */
215   }


Overlay http tunnel socket with SSL socket

After you have successfully created the tunneling socket, you overlay it with the SSL socket. Again, this is not difficult to do:

  • Print
  • Feedback

Resources