Creating Secure Server Sockets
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 theKeyStore
and its pass phrase. - Initialize the context with the necessary key managers from the
KeyManagerFactory
, trust managers from theTrustManagerFactory
, 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 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:
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.oracle.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.oracle.com/j2se/1.4.2/docs/guide/security/CryptoSpec.html, or the previously mentioned tutorials Java Cryptography, by Jonathan Knudsen, or Java Security, by Scott Oaks (both from Oracle). 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.
SecureOrderTaker
for accepting orders and printing them on System.out
. Of course, in a real app, 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
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):