Java Network Programming, Third Edition

     

If you don't care very much about the underlying details, using an encrypted SSL socket to talk to an existing secure server is truly straightforward. Rather than constructing a java.net.Socket object with a constructor, you get one from a javax.net.ssl.SSLSocketFactory using its createSocket() method. SSLSocketFactory is an abstract class that follows the abstract factory design pattern:

public abstract class SSLSocketFactory extends SocketFactory

Since the SSLFactorySocket class is itself abstract, you get an instance of it by invoking the static SSLSocketFactory.getDefault() method:

public static SocketFactory getDefault( ) throws InstantiationException

This either returns an instance of SSLSocketFactory or throws an InstantiationException if no concrete subclass can be found. Once you have a reference to the factory, use one of these five overloaded createSocket( ) methods to build an SSLSocket :

public abstract Socket createSocket(String host, int port) throws IOException, UnknownHostException public abstract Socket createSocket(InetAddress host, int port) throws IOException public abstract Socket createSocket(String host, int port, InetAddress interface, int localPort) throws IOException, UnknownHostException public abstract Socket createSocket(InetAddress host, int port, InetAddress interface, int localPort) throws IOException, UnknownHostException public abstract Socket createSocket(Socket proxy, String host, int port, boolean autoClose) throws IOException

The first two methods create and return a socket that's connected to the specified host and port or throw an IOException if they can't connect. The third and fourth methods connect and return a socket that's connected to the specified host and port from the specified local network interface and port. The last createSocket( ) method, however, is a little different. It begins with an existing Socket object that's connected to a proxy server. It returns a Socket that tunnels through this proxy server to the specified host and port. The autoClose argument determines whether the underlying proxy socket should be closed when this socket is closed. If autoClose is true , the underlying socket will be closed; if false , it won't be.

The Socket that all these methods return will really be a javax.net.ssl.SSLSocket , a subclass of java.net.Socket . However, you don't need to know that. Once the secure socket has been created, you use it just like any other socket, through its getInputStream( ) , getOutputStream() , and other methods. For example, let's suppose there's a server running on login. ibiblio .org on port 7,000 that accepts orders. Each order is sent as an ASCII string using a single TCP connection. The server accepts the order and closes the connection. (I'm leaving out a lot of details that would be necessary in a real-world system, such as the server sending a response code telling the client whether the order was accepted.) The orders that clients send look like this:

Name: John Smith Product-ID: 67X-89 Address: 1280 Deniston Blvd, NY NY 10003 Card number: 4000-1234-5678-9017 Expires: 08/05

There's enough information in this message to let someone snooping packets use John Smith's credit card number for nefarious purposes. Consequently, before sending this order, you should encrypt it; the simplest way to do that without burdening either the server or the client with a lot of complicated, error-prone encryption code is to use a secure socket. The following code sends the order over a secure socket:

try { // This statement is only needed if you didn't add // security.provider.3=com.sun.net.ssl.internal.ssl.Provider // to your java.security file. Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider( )); SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault( ); Socket socket = factory.createSocket("login.metalab.unc.edu", 7000); Writer out = new OutputStreamWriter(socket.getOutputStream( ), "ASCII"); out.write("Name: John Smith\r\n"); out.write("Product-ID: 67X-89\r\n"); out.write("Address: 1280 Deniston Blvd, NY NY 10003\r\n"); out.write("Card number: 4000-1234-5678-9017\r\n"); out.write("Expires: 08/05\r\n"); out.flush( ); out.close( ); socket.close( ); } catch (IOException ex) { ex.printStackTrace( ); }

Only the first three statements are noticeably different from what you'd do with an insecure socket. The rest of the code just uses the normal methods of the Socket , OutputStream , and Writer classes.

Reading input is no harder. Example 11-1 is a simple program that connects to a secure HTTP server, sends a simple GET request, and prints out the response.

Example 11-1. HTTPSClient

import java.net.*; import java.io.*; import java.security.*; import javax.net.ssl.*; import com.macfaq.io.*; public class HTTPSClient { public static void main(String[] args) { if (args.length == 0) { System.out.println("Usage: java HTTPSClient2 host"); return; } int port = 443; // default https port String host = args[0]; try { SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault( ); SSLSocket socket = (SSLSocket) factory.createSocket(host, port); // enable all the suites String[] supported = socket.getSupportedCipherSuites( ); socket.setEnabledCipherSuites(supported); Writer out = new OutputStreamWriter(socket.getOutputStream( )); // https requires the full URL in the GET line out.write("GET http://" + host + "/ HTTP/1.1\r\n"); out.write("Host: " + host + "\r\n"); out.write("\r\n"); out.flush( ); // read response BufferedReader in = new SafeBufferedReader( new InputStreamReader(socket.getInputStream( ))); // read the header String s; while (!(s = in.readLine( )).equals("")) { System.out.println(s); } System.out.println( ); // read the length String contentLength = in.readLine( ); int length = Integer.MAX_VALUE; try { length = Integer.parseInt(contentLength.trim( ), 16); } catch (NumberFormatException ex) { // This server doesn't send the content-length // in the first line of the response body } System.out.println(contentLength); int c; int i = 0; while ((c = in.read( )) != -1 && i++ < length) { System.out.write(c); } System.out.println( ); out.close( ); in.close( ); socket.close( ); } catch (IOException ex) { System.err.println(ex); } } }

Here are the first few lines of output you get when you connect to the U.S. Postal Service's web site:

% java HTTPSClient www.usps.com HTTP/1.1 200 OK Server: Netscape-Enterprise/6.0 Date: Wed, 28 Jan 2004 18:13:08 GMT Content-type: text/html Set-Cookie: WEBTRENDS_ID=216.254.85.72-1075313584.16566; expires=Fri, 31-Dec-2010 00:00:00 GMT; path=/ Transfer-Encoding: chunked b6b <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8"> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <link rel="stylesheet" href="/common/stylesheets/styles.css" type="text/css"> <TITLE>USPS - The United States Postal Service (U.S. Postal Service)</TITLE>

When this program was tested for this edition, it initially refused to connect to www.usps.com because it couldn't verify the identity of the remote server. The problem was that the root certificates shipped with the version of the JDK I was using (1.4.2_02-b3) had expired . Upgrading to the latest minor version (1.4.2_03-b2) fixed the problem. If you see any exception messages like "No trusted certificate found", try upgrading to the latest minor version of either the JDK (if you're using 1.4 or later) or the JSSE (if you're using Java 1.3 or earlier).

One thing you may notice when you run this program is that it's slower to respond than you might expect. There's a noticeable amount of both CPU and network overhead involved in generating and exchanging the public keys. Even over a fast connection, it can easily take 10 seconds or more for the connection to be established. Consequently, you probably don't want to serve all your content over HTTPS, only the content that really needs to be private.

Категории