fSecure client sockets are only half of the equation. The other half is SSL-enabled server sockets. These are instances of the javax.net.SSLServerSocket class: public abstract class SSLServerSocket extends ServerSocket Like SSLSocket , all the constructors in this class are protected. Like SSLSocket , instances of SSLServerSocket are created by an abstract factory class, javax.net.SSLServerSocketFactory : public abstract class SSLServerSocketFactory extends ServerSocketFactory Also like SSLSocketFactory , an instance of SSLServerSocketFactory is returned by a static SSLServerSocketFactory.getDefault( ) method: public static ServerSocketFactory getDefault( ) And like SSLSocketFactory , SSLServerSocketFactory has three overloaded createServerSocket( ) methods that return instances of SSLServerSocket and are easily understood by analogy with the java.net.ServerSocket constructors: public abstract ServerSocket createServerSocket(int port) throws IOException public abstract ServerSocket createServerSocket(int port, int queueLength) throws IOException public abstract ServerSocket createServerSocket(int port, int queueLength, InetAddress interface) throws IOException If that were all there was to creating secure server sockets, they would be quite straightforward and simple to use. Unfortunately, that's not all there is to it. The factory that SSLServerSocketFactory.getDefault( ) returns generally only supports server authentication. It does not support encryption. To get encryption as well, server-side secure sockets require more initialization and setup. Exactly how this setup is performed is implementation-dependent. In Sun's reference implementation, a com.sun.net.ssl.SSLContext object is responsible for creating fully configured and initialized secure server sockets. The details vary from JSSE implementation to JSSE implementation, but to create a secure server socket in the reference implementation, you have to: -
Generate public keys and certificates using keytool . -
Pay money to have your certificates authenticated by a trusted third party such as Verisign. -
Create an SSLContext for the algorithm you'll use. -
Create a TrustManagerFactory for the source of certificate material you'll be using. -
Create a KeyManagerFactory for the type of key material you'll be using. -
Create a KeyStore object for the key and certificate database. (Sun's default is JKS.) -
Fill the KeyStore object with keys and certificates; for instance, by loading them from the filesystem using the pass phrase they're encrypted with. -
Initialize the KeyManagerFactory with the KeyStore and its pass phrase. -
Initialize the context with the necessary key managers from the KeyManagerFactory , trust managers from the TrustManagerFactory , and a source of randomness. (The last two can be null if you're willing to accept the defaults.) Example 11-2 demonstrates this procedure with a complete SecureOrderTaker for accepting orders and printing them on System.out . Of course, in a real application, you'd do something more interesting with the orders. Example 11-2. SecureOrderTaker import java.net.*; import java.io.*; import java.util.*; import java.security.*; import javax.net.ssl.*; import javax.net.*; public class SecureOrderTaker { public final static int DEFAULT_PORT = 7000; public final static String algorithm = "SSL"; public static void main(String[] args) { int port = DEFAULT_PORT; if (args.length > 0) { try { port = Integer.parseInt(args[0]); if (port < 0 port >= 65536) { System.out.println("Port must between 0 and 65535"); return; } } catch (NumberFormatException ex) {} } try { SSLContext context = SSLContext.getInstance(algorithm); // The reference implementation only supports X.509 keys KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); // Sun's default kind of key store KeyStore ks = KeyStore.getInstance("JKS"); // For security, every key store is encrypted with a // pass phrase that must be provided before we can load // it from disk. The pass phrase is stored as a char[] array // so it can be wiped from memory quickly rather than // waiting for a garbage collector. Of course using a string // literal here completely defeats that purpose. char[] password = "2andnotafnord".toCharArray( ); ks.load(new FileInputStream("jnp3e.keys"), password); kmf.init(ks, password); // context.init(kmf.getKeyManagers( ), null, null); SSLServerSocketFactory factory = context.getServerSocketFactory( ); SSLServerSocket server = (SSLServerSocket) factory.createServerSocket(port); String[] supported = server.getSupportedCipherSuites( ); String[] anonCipherSuitesSupported = new String[supported.length]; int numAnonCipherSuitesSupported = 0; for (int i = 0; i < supported.length; i++) { if (supported[i].indexOf("_anon_") > 0) { anonCipherSuitesSupported[numAnonCipherSuitesSupported++] = supported[i]; } } String[] oldEnabled = server.getEnabledCipherSuites( ); String[] newEnabled = new String[oldEnabled.length + numAnonCipherSuitesSupported]; System.arraycopy(oldEnabled, 0, newEnabled, 0, oldEnabled.length); System.arraycopy(anonCipherSuitesSupported, 0, newEnabled, oldEnabled.length, numAnonCipherSuitesSupported); server.setEnabledCipherSuites(newEnabled); // Now all the set up is complete and we can focus // on the actual communication. try { while (true) { // This socket will be secure, // but there's no indication of that in the code! Socket theConnection = server.accept( ); InputStream in = theConnection.getInputStream( ); int c; while ((c = in.read( )) != -1) { System.out.write(c); } theConnection.close( ); } // end while } // end try catch (IOException ex) { System.err.println(ex); } // end catch } // end try catch (IOException ex) { ex.printStackTrace( ); } // end catch catch (KeyManagementException ex) { ex.printStackTrace( ); } // end catch catch (KeyStoreException ex) { ex.printStackTrace( ); } // end catch catch (NoSuchAlgorithmException ex) { ex.printStackTrace( ); } // end catch catch (java.security.cert.CertificateException ex) { ex.printStackTrace( ); } // end catch catch (UnrecoverableKeyException ex) { ex.printStackTrace( ); } // end catch } // end main } // end server This example loads the necessary keys and certificates from a file named jnp3e.keys in the current working directory protected with the password "2andnotafnord". What this example doesn't show you is how that file was created. It was built with the keytool program that's bundled with the JDK like this: D:\JAVA> keytool -genkey -alias ourstore -keystore jnp3e.keys Enter keystore password: 2andnotafnord What is your first and last name? [Unknown]: Elliotte What is the name of your organizational unit? [Unknown]: Me, Myself, and I What is the name of your organization? [Unknown]: Cafe au Lait What is the name of your City or Locality? [Unknown]: Brooklyn What is the name of your State or Province? [Unknown]: New York What is the two-letter country code for this unit? [Unknown]: NY Is <CN=Elliotte, OU="Me, Myself, and I", O=Cafe au Lait, L=Brooklyn, ST=New York, C=NY> correct? [no]: y Enter key password for <ourstore> (RETURN if same as keystore password): When this is finished, you'll have a file named jnp3e.keys , which contains your public keys. However, no one will believe that these are your public keys unless you have them certified by a trusted third party such as Verisign (http://www.verisign.com/). Unfortunately, this certification costs money. The cheapest option is $14.95 per year for a Class 1 Digital ID. Verisign hides the sign-up form for this kind of ID deep within its web site, apparently to get you to sign up for the much more expensive options that are prominently featured on its home page. At the time of this writing, the sign-up form is at https ://www.verisign.com/client/. Verisign has changed this URL several times in the past, making it much harder to find than its more expensive options. In the more expensive options, Verisign goes to greater lengths to guarantee that you are who you say you are. Before signing up for any kind of digital ID, you should be aware that purchasing one has potentially severe legal consequences. In some jurisdictions, poorly thought-out laws make digital ID owners liable for all purchases made and contracts signed using their digital ID, regardless of whether the ID was stolen or forged. If you just want to explore the JSSE before deciding whether to go through the hassle, expense, and liability of purchasing a verified certificate, Sun includes a verified keystore file called testkeys , protected with the password "passphrase", that has some JSSE samples (http://java.sun.com/products/jsse/). However, this isn't good enough for real work. For more information about exactly what's going on and what the various options are, as well as other ways to create key and certificate files, consult the online documentation for the keytool utility that came with your JDK, the Java Cryptography Architecture guide at http://java.sun.com/j2se/1.4.2/docs/guide/security/CryptoSpec.html, or the previously mentioned books Java Cryptography , by Jonathan Knudsen, or Java Security , by Scott Oaks (both from O'Reilly). Another approach is to use cipher suites that don't require authentication. There are six of these in Sun's JDK 1.4: SSL_DH_anon_WITH_RC4_128_MD5, TLS_DH_anon_WITH_AES_128_CBC_SHA, SSL_DH_anon_WITH_3DES_EDE_CBC_SHA, SSL_DH_anon_WITH_DES_CBC_SHA, SSL_DH_anon_EXPORT_WITH_RC4_40_MD5, and SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA. These are not enabled by default because they're vulnerable to a man-in-the-middle attack, but at least they allow you to write simple programs without paying Verisign any money. |